Repository: bpmn-io/bpmn-js
Branch: develop
Commit: 0c1e5560a0cb
Files: 766
Total size: 4.1 MB
Directory structure:
gitextract_zf5udxc2/
├── .github/
│ ├── CONTRIBUTING.md
│ ├── ISSUE_TEMPLATE/
│ │ ├── BUG_REPORT.md
│ │ ├── FEATURE_REQUEST.md
│ │ ├── TASK.md
│ │ └── config.yml
│ ├── merge-me.yml
│ └── workflows/
│ ├── CI.yml
│ ├── CODE_SCANNING.yml
│ ├── COMMENT_TARGETS_MAIN.yml
│ ├── MERGE_MAIN_TO_DEVELOP.yml
│ ├── POST_RELEASE.yml
│ └── RELEASE.yml
├── .gitignore
├── .release-please-manifest.json
├── CHANGELOG.md
├── LICENSE
├── README.md
├── assets/
│ └── bpmn-js.css
├── docs/
│ ├── project/
│ │ ├── SETUP.md
│ │ ├── setup-alternative.sh
│ │ ├── setup.bat
│ │ └── setup.sh
│ └── translations.json
├── eslint.config.mjs
├── lib/
│ ├── BaseModeler.js
│ ├── BaseModeler.spec.ts
│ ├── BaseViewer.js
│ ├── BaseViewer.spec.ts
│ ├── Modeler.js
│ ├── Modeler.spec.ts
│ ├── NavigatedViewer.js
│ ├── NavigatedViewer.spec.ts
│ ├── Viewer.js
│ ├── Viewer.spec.ts
│ ├── core/
│ │ └── index.js
│ ├── draw/
│ │ ├── BpmnRenderUtil.js
│ │ ├── BpmnRenderer.js
│ │ ├── PathMap.js
│ │ ├── TextRenderer.js
│ │ ├── TextRenderer.spec.ts
│ │ └── index.js
│ ├── features/
│ │ ├── align-elements/
│ │ │ ├── AlignElementsContextPadProvider.js
│ │ │ ├── AlignElementsIcons.js
│ │ │ ├── AlignElementsMenuProvider.js
│ │ │ ├── BpmnAlignElements.js
│ │ │ └── index.js
│ │ ├── append-preview/
│ │ │ ├── AppendPreview.js
│ │ │ └── index.js
│ │ ├── auto-place/
│ │ │ ├── BpmnAutoPlace.js
│ │ │ ├── BpmnAutoPlaceUtil.js
│ │ │ └── index.js
│ │ ├── auto-resize/
│ │ │ ├── BpmnAutoResize.js
│ │ │ ├── BpmnAutoResizeProvider.js
│ │ │ └── index.js
│ │ ├── context-pad/
│ │ │ ├── ContextPadProvider.js
│ │ │ └── index.js
│ │ ├── copy-paste/
│ │ │ ├── BpmnCopyPaste.js
│ │ │ ├── ModdleCopy.js
│ │ │ └── index.js
│ │ ├── di-ordering/
│ │ │ ├── BpmnDiOrdering.js
│ │ │ └── index.js
│ │ ├── distribute-elements/
│ │ │ ├── BpmnDistributeElements.js
│ │ │ ├── DistributeElementsIcons.js
│ │ │ ├── DistributeElementsMenuProvider.js
│ │ │ └── index.js
│ │ ├── drilldown/
│ │ │ ├── DrilldownBreadcrumbs.js
│ │ │ ├── DrilldownCentering.js
│ │ │ ├── DrilldownOverlayBehavior.js
│ │ │ ├── SubprocessCompatibility.js
│ │ │ └── index.js
│ │ ├── editor-actions/
│ │ │ ├── BpmnEditorActions.js
│ │ │ └── index.js
│ │ ├── grid-snapping/
│ │ │ ├── BpmnGridSnapping.js
│ │ │ ├── behavior/
│ │ │ │ ├── GridSnappingAutoPlaceBehavior.js
│ │ │ │ ├── GridSnappingLayoutConnectionBehavior.js
│ │ │ │ ├── GridSnappingParticipantBehavior.js
│ │ │ │ └── index.js
│ │ │ └── index.js
│ │ ├── interaction-events/
│ │ │ ├── BpmnInteractionEvents.js
│ │ │ └── index.js
│ │ ├── keyboard/
│ │ │ ├── BpmnKeyboardBindings.js
│ │ │ └── index.js
│ │ ├── label-editing/
│ │ │ ├── LabelEditingPreview.js
│ │ │ ├── LabelEditingProvider.js
│ │ │ ├── LabelUtil.js
│ │ │ ├── cmd/
│ │ │ │ └── UpdateLabelHandler.js
│ │ │ └── index.js
│ │ ├── label-link/
│ │ │ ├── LabelLink.js
│ │ │ └── index.js
│ │ ├── modeling/
│ │ │ ├── BpmnFactory.js
│ │ │ ├── BpmnLayouter.js
│ │ │ ├── BpmnUpdater.js
│ │ │ ├── ElementFactory.js
│ │ │ ├── ElementFactory.test.ts
│ │ │ ├── Modeling.js
│ │ │ ├── Modeling.test.ts
│ │ │ ├── behavior/
│ │ │ │ ├── AdaptiveLabelPositioningBehavior.js
│ │ │ │ ├── AppendBehavior.js
│ │ │ │ ├── AssociationBehavior.js
│ │ │ │ ├── AttachEventBehavior.js
│ │ │ │ ├── BoundaryEventBehavior.js
│ │ │ │ ├── CompensateBoundaryEventBehavior.js
│ │ │ │ ├── CreateBehavior.js
│ │ │ │ ├── CreateDataObjectBehavior.js
│ │ │ │ ├── CreateParticipantBehavior.js
│ │ │ │ ├── DataInputAssociationBehavior.js
│ │ │ │ ├── DataStoreBehavior.js
│ │ │ │ ├── DeleteLaneBehavior.js
│ │ │ │ ├── DetachEventBehavior.js
│ │ │ │ ├── DropOnFlowBehavior.js
│ │ │ │ ├── EventBasedGatewayBehavior.js
│ │ │ │ ├── FixHoverBehavior.js
│ │ │ │ ├── GroupBehavior.js
│ │ │ │ ├── ImportDockingFix.js
│ │ │ │ ├── IsHorizontalFix.js
│ │ │ │ ├── LabelBehavior.js
│ │ │ │ ├── LayoutConnectionBehavior.js
│ │ │ │ ├── MessageFlowBehavior.js
│ │ │ │ ├── NonInterruptingBehavior.js
│ │ │ │ ├── RemoveElementBehavior.js
│ │ │ │ ├── RemoveEmbeddedLabelBoundsBehavior.js
│ │ │ │ ├── RemoveParticipantBehavior.js
│ │ │ │ ├── ReplaceConnectionBehavior.js
│ │ │ │ ├── ReplaceElementBehaviour.js
│ │ │ │ ├── ResizeBehavior.js
│ │ │ │ ├── ResizeLaneBehavior.js
│ │ │ │ ├── RootElementReferenceBehavior.js
│ │ │ │ ├── SetCompensationActivityAfterPasteBehavior.js
│ │ │ │ ├── SpaceToolBehavior.js
│ │ │ │ ├── SubProcessPlaneBehavior.js
│ │ │ │ ├── SubProcessStartEventBehavior.js
│ │ │ │ ├── TextAnnotationBehavior.js
│ │ │ │ ├── ToggleCollapseConnectionBehaviour.js
│ │ │ │ ├── ToggleElementCollapseBehaviour.js
│ │ │ │ ├── UnclaimIdBehavior.js
│ │ │ │ ├── UnsetDefaultFlowBehavior.js
│ │ │ │ ├── UpdateFlowNodeRefsBehavior.js
│ │ │ │ ├── index.js
│ │ │ │ └── util/
│ │ │ │ ├── CategoryUtil.js
│ │ │ │ ├── ConnectionLayoutUtil.js
│ │ │ │ ├── GeometricUtil.js
│ │ │ │ ├── LabelLayoutUtil.js
│ │ │ │ ├── LayoutUtil.js
│ │ │ │ ├── LineAttachmentUtil.js
│ │ │ │ ├── LineIntersect.js
│ │ │ │ ├── NonInterruptingUtil.js
│ │ │ │ └── ResizeUtil.js
│ │ │ ├── cmd/
│ │ │ │ ├── AddLaneHandler.js
│ │ │ │ ├── IdClaimHandler.js
│ │ │ │ ├── ResizeLaneHandler.js
│ │ │ │ ├── SetColorHandler.js
│ │ │ │ ├── SplitLaneHandler.js
│ │ │ │ ├── UpdateCanvasRootHandler.js
│ │ │ │ ├── UpdateFlowNodeRefsHandler.js
│ │ │ │ ├── UpdateModdlePropertiesHandler.js
│ │ │ │ ├── UpdatePropertiesHandler.js
│ │ │ │ └── UpdateSemanticParentHandler.js
│ │ │ ├── index.js
│ │ │ └── util/
│ │ │ ├── LaneUtil.js
│ │ │ └── ModelingUtil.js
│ │ ├── modeling-feedback/
│ │ │ ├── ModelingFeedback.js
│ │ │ └── index.js
│ │ ├── ordering/
│ │ │ ├── BpmnOrderingProvider.js
│ │ │ └── index.js
│ │ ├── outline/
│ │ │ ├── OutlineProvider.js
│ │ │ ├── OutlineUtil.js
│ │ │ └── index.js
│ │ ├── palette/
│ │ │ ├── PaletteProvider.js
│ │ │ └── index.js
│ │ ├── popup-menu/
│ │ │ ├── ReplaceMenuProvider.js
│ │ │ ├── index.js
│ │ │ └── util/
│ │ │ ├── Icons.js
│ │ │ └── TypeUtil.js
│ │ ├── replace/
│ │ │ ├── BpmnReplace.js
│ │ │ ├── ReplaceOptions.js
│ │ │ └── index.js
│ │ ├── replace-preview/
│ │ │ ├── BpmnReplacePreview.js
│ │ │ └── index.js
│ │ ├── rules/
│ │ │ ├── BpmnRules.js
│ │ │ └── index.js
│ │ ├── search/
│ │ │ ├── BpmnSearchProvider.js
│ │ │ └── index.js
│ │ ├── snapping/
│ │ │ ├── BpmnConnectSnapping.js
│ │ │ ├── BpmnCreateMoveSnapping.js
│ │ │ ├── BpmnSnappingUtil.js
│ │ │ └── index.js
│ │ └── space-tool/
│ │ ├── BpmnSpaceTool.js
│ │ └── index.js
│ ├── import/
│ │ ├── BpmnImporter.js
│ │ ├── BpmnTreeWalker.js
│ │ ├── Importer.js
│ │ ├── Util.js
│ │ └── index.js
│ ├── index.js
│ ├── model/
│ │ └── Types.ts
│ └── util/
│ ├── AnnotationUtil.js
│ ├── CompatibilityUtil.js
│ ├── DiUtil.js
│ ├── DrilldownUtil.js
│ ├── LabelUtil.js
│ ├── ModelUtil.js
│ ├── PoweredByUtil.js
│ ├── Types.js
│ └── Types.ts
├── package.json
├── renovate.json
├── resources/
│ ├── banner-min.txt
│ ├── banner.txt
│ └── initial.bpmn
├── rollup.config.js
├── tasks/
│ ├── build-distro.mjs
│ ├── stages/
│ │ ├── await-published
│ │ ├── update-demo
│ │ ├── update-examples
│ │ ├── update-integration-test
│ │ ├── update-translations
│ │ └── update-website
│ └── test-distro.mjs
├── test/
│ ├── TestHelper.js
│ ├── config/
│ │ ├── karma.distro.js
│ │ ├── karma.unit.js
│ │ └── translation-reporter.js
│ ├── coverageBundle.js
│ ├── distro/
│ │ ├── bpmn-modeler.js
│ │ ├── bpmn-navigated-viewer.js
│ │ ├── bpmn-viewer.js
│ │ └── helper.js
│ ├── fixtures/
│ │ ├── bpmn/
│ │ │ ├── align-elements.bpmn
│ │ │ ├── basic.bpmn
│ │ │ ├── boundary-events.bpmn
│ │ │ ├── collaboration/
│ │ │ │ ├── collaboration-data-store.bpmn
│ │ │ │ ├── collaboration-empty-participant.bpmn
│ │ │ │ ├── collaboration-message-flows.bpmn
│ │ │ │ ├── collaboration-participant.bpmn
│ │ │ │ ├── process-empty.bpmn
│ │ │ │ └── process.bpmn
│ │ │ ├── collaboration-data-items.bpmn
│ │ │ ├── collaboration-message-flows.bpmn
│ │ │ ├── collaboration-sequence-flows.bpmn
│ │ │ ├── collaboration-vertical.bpmn
│ │ │ ├── collaboration.bpmn
│ │ │ ├── collapsed-sub-process-legacy.bpmn
│ │ │ ├── collapsed-sub-process.bpmn
│ │ │ ├── complex.bpmn
│ │ │ ├── conditions.bpmn
│ │ │ ├── containers.bpmn
│ │ │ ├── distribute-elements-filtering.bpmn
│ │ │ ├── distribute-elements-filtering.collaboration.bpmn
│ │ │ ├── distribute-elements.bpmn
│ │ │ ├── draw/
│ │ │ │ ├── activity-markers-combination.bpmn
│ │ │ │ ├── activity-markers-simple.bpmn
│ │ │ │ ├── activity-markers.bpmn
│ │ │ │ ├── associations.bpmn
│ │ │ │ ├── boundary-event-with-refnode.bpmn
│ │ │ │ ├── boundary-event-without-refnode.bpmn
│ │ │ │ ├── boundary-event-z-index.bpmn
│ │ │ │ ├── call-activity.bpmn
│ │ │ │ ├── conditional-flow-default.bpmn
│ │ │ │ ├── conditional-flow-gateways.bpmn
│ │ │ │ ├── conditional-flow-typed-task.bpmn
│ │ │ │ ├── conditional-flow.bpmn
│ │ │ │ ├── data-objects.bpmn
│ │ │ │ ├── event-subprocess-icons.bpmn
│ │ │ │ ├── event-subprocesses-collapsed.bpmn
│ │ │ │ ├── event-subprocesses-expanded.bpmn
│ │ │ │ ├── events-interrupting.bpmn
│ │ │ │ ├── events.bpmn
│ │ │ │ ├── gateway-type-default.bpmn
│ │ │ │ ├── gateways.bpmn
│ │ │ │ ├── group-name.bpmn
│ │ │ │ ├── group.bpmn
│ │ │ │ ├── message-label.bpmn
│ │ │ │ ├── message-marker.bpmn
│ │ │ │ ├── pools-with-collection-marker.bpmn
│ │ │ │ ├── pools.bpmn
│ │ │ │ ├── task-types.bpmn
│ │ │ │ ├── text-annotation.bpmn
│ │ │ │ ├── vertical-pools.bpmn
│ │ │ │ └── xor.bpmn
│ │ │ ├── empty-definitions.bpmn
│ │ │ ├── error/
│ │ │ │ ├── categoryValue.bpmn
│ │ │ │ ├── di-plane-no-bpmn-element.bpmn
│ │ │ │ ├── duplicate-ids.bpmn
│ │ │ │ ├── invalid-child.bpmn
│ │ │ │ ├── missing-namespace.bpmn
│ │ │ │ ├── no-process-collaboration.bpmn
│ │ │ │ └── no-xml.txt
│ │ │ ├── event-sub-processes.bpmn
│ │ │ ├── extension/
│ │ │ │ ├── camunda.bpmn
│ │ │ │ ├── custom-override.bpmn
│ │ │ │ └── custom.bpmn
│ │ │ ├── features/
│ │ │ │ ├── drop/
│ │ │ │ │ ├── drop.bpmn
│ │ │ │ │ └── recursive-task.bpmn
│ │ │ │ ├── replace/
│ │ │ │ │ ├── 01_replace.bpmn
│ │ │ │ │ ├── association-gateways.bpmn
│ │ │ │ │ ├── cancel-events.bpmn
│ │ │ │ │ ├── connection.bpmn
│ │ │ │ │ ├── copy-properties.bpmn
│ │ │ │ │ ├── data-elements.bpmn
│ │ │ │ │ ├── data-stores-positioned-against-participant.bpmn
│ │ │ │ │ └── participants.bpmn
│ │ │ │ └── rules/
│ │ │ │ ├── event-based-gateway-outgoing-edge.bpmn
│ │ │ │ ├── link-event.bpmn
│ │ │ │ └── text-annotation-association.bpmn
│ │ │ ├── flow-markers.bpmn
│ │ │ ├── import/
│ │ │ │ ├── boundaryEvent.bpmn
│ │ │ │ ├── collaboration.bpmn
│ │ │ │ ├── collapsed/
│ │ │ │ │ ├── collaboration.bpmn
│ │ │ │ │ ├── process.bpmn
│ │ │ │ │ └── processWithChildren.bpmn
│ │ │ │ ├── collapsed-subprocess.bpmn
│ │ │ │ ├── data-store.inside-participant.bpmn
│ │ │ │ ├── data-store.outside-participant.dangling.bpmn
│ │ │ │ ├── data-store.outside-participant.participant.bpmn
│ │ │ │ ├── data-store.outside-participant.subprocess.bpmn
│ │ │ │ ├── default-attrs.bpmn
│ │ │ │ ├── error/
│ │ │ │ │ ├── boundaryEvent-invalidAttachToRef.bpmn
│ │ │ │ │ ├── boundaryEvent-missingAttachToRef.bpmn
│ │ │ │ │ ├── dangling-process-message-flow.bpmn
│ │ │ │ │ ├── invalid-flow-element.bpmn
│ │ │ │ │ └── multiple-dis.bpmn
│ │ │ │ ├── groups.bpmn
│ │ │ │ ├── labels/
│ │ │ │ │ ├── collaboration-message-flows.bpmn
│ │ │ │ │ ├── collaboration.bpmn
│ │ │ │ │ ├── embedded.bpmn
│ │ │ │ │ ├── external-no-di.bpmn
│ │ │ │ │ └── external.bpmn
│ │ │ │ ├── multiple-diagrams.bpmn
│ │ │ │ ├── position/
│ │ │ │ │ └── position-testcase.bpmn
│ │ │ │ ├── process.bpmn
│ │ │ │ └── text-annotation-message-flow.bpmn
│ │ │ ├── kitchen-sink.bpmn
│ │ │ ├── multiple-diagrams-lanesets.bpmn
│ │ │ ├── multiple-diagrams-overlapping-di.bpmn
│ │ │ ├── multiple-diagrams.bpmn
│ │ │ ├── multiple-nested-processes.bpmn
│ │ │ ├── nested-subprocesses.bpmn
│ │ │ ├── sequence-flows.bpmn
│ │ │ ├── simple-resizable.bpmn
│ │ │ └── simple.bpmn
│ │ └── json/
│ │ └── model/
│ │ ├── camunda.json
│ │ ├── custom-override.json
│ │ └── custom.json
│ ├── helper/
│ │ ├── TranslationCollector.js
│ │ └── index.js
│ ├── integration/
│ │ ├── CustomElementsSpec.js
│ │ ├── ReimportSpec.js
│ │ ├── SimpleModelingSpec.js
│ │ ├── custom-elements/
│ │ │ ├── CustomElementFactory.js
│ │ │ ├── CustomRenderer.js
│ │ │ ├── CustomRules.js
│ │ │ ├── CustomUpdater.js
│ │ │ └── index.js
│ │ └── model/
│ │ └── BpmnModdleSpec.js
│ ├── matchers/
│ │ ├── BoundsMatchers.js
│ │ ├── ConnectionMatchers.js
│ │ └── JSONMatcher.js
│ ├── spec/
│ │ ├── BaseModelerSpec.js
│ │ ├── BaseViewerSpec.js
│ │ ├── Modeler.copy-paste.a.bpmn
│ │ ├── Modeler.copy-paste.b.bpmn
│ │ ├── Modeler.copy-paste.complex.bpmn
│ │ ├── Modeler.copy-paste.empty.bpmn
│ │ ├── ModelerSpec.js
│ │ ├── NavigatedViewerSpec.js
│ │ ├── ViewerSpec.js
│ │ ├── draw/
│ │ │ ├── BpmnRenderUtilSpec.js
│ │ │ ├── BpmnRenderer.colors.bpmn
│ │ │ ├── BpmnRenderer.connection-colors.bpmn
│ │ │ ├── BpmnRenderer.group-colors.bpmn
│ │ │ ├── BpmnRenderer.labels.bpmn
│ │ │ ├── BpmnRenderer.no-event-icons.bpmn
│ │ │ ├── BpmnRenderer.sequenceFlow-no-source.bpmn
│ │ │ ├── BpmnRenderer.simple-cropping.bpmn
│ │ │ ├── BpmnRendererSpec.js
│ │ │ ├── TextRenderer.bpmn
│ │ │ ├── TextRendererSpec.js
│ │ │ └── custom-renderer/
│ │ │ ├── CustomRenderer.js
│ │ │ └── index.js
│ │ ├── environment/
│ │ │ └── MockingSpec.js
│ │ ├── features/
│ │ │ ├── align-elements/
│ │ │ │ ├── AlignElementsContextPadProviderSpec.js
│ │ │ │ ├── AlignElementsMenuProviderSpec.js
│ │ │ │ └── BpmnAlignElementsSpec.js
│ │ │ ├── append-preview/
│ │ │ │ ├── AppendPreview.bpmn
│ │ │ │ └── AppendPreviewSpec.js
│ │ │ ├── auto-place/
│ │ │ │ ├── BpmnAutoPlace.boundary-events.bpmn
│ │ │ │ ├── BpmnAutoPlace.bpmn
│ │ │ │ ├── BpmnAutoPlace.multi-connection.bpmn
│ │ │ │ ├── BpmnAutoPlace.subprocess.bpmn
│ │ │ │ ├── BpmnAutoPlace.subprocess.horizontal.bpmn
│ │ │ │ ├── BpmnAutoPlace.subprocess.vertical.bpmn
│ │ │ │ ├── BpmnAutoPlace.vertical.boundary-events.bpmn
│ │ │ │ ├── BpmnAutoPlace.vertical.bpmn
│ │ │ │ ├── BpmnAutoPlace.vertical.multi-connection.bpmn
│ │ │ │ └── BpmnAutoPlaceSpec.js
│ │ │ ├── auto-resize/
│ │ │ │ ├── AutoResize.lanes.bpmn
│ │ │ │ ├── AutoResize.multi-selection.bpmn
│ │ │ │ ├── AutoResize.nested-sub-processes.bpmn
│ │ │ │ ├── AutoResize.participant.bpmn
│ │ │ │ ├── AutoResize.space-tool.bpmn
│ │ │ │ ├── AutoResize.sub-processes.bpmn
│ │ │ │ └── AutoResizeSpec.js
│ │ │ ├── context-pad/
│ │ │ │ ├── ContextPad.activation.bpmn
│ │ │ │ └── ContextPadProviderSpec.js
│ │ │ ├── copy-paste/
│ │ │ │ ├── BpmnCopyPasteSpec.js
│ │ │ │ ├── ModdleCopySpec.js
│ │ │ │ ├── basic.bpmn
│ │ │ │ ├── collaboration-multiple.bpmn
│ │ │ │ ├── collaboration.bpmn
│ │ │ │ ├── collapsed-subprocess.bpmn
│ │ │ │ ├── complex.bpmn
│ │ │ │ ├── copy-properties.bpmn
│ │ │ │ ├── data-associations.bpmn
│ │ │ │ ├── event-based-gateway.bpmn
│ │ │ │ ├── nested-subprocess-annotations.bpmn
│ │ │ │ └── properties.bpmn
│ │ │ ├── distribute-elements/
│ │ │ │ ├── BpmnDistributeElementsSpec.js
│ │ │ │ └── DistributeElementsMenuProviderSpec.js
│ │ │ ├── drilldown/
│ │ │ │ ├── DrilldownIntegrationSpec.js
│ │ │ │ ├── DrilldownOverlayBehaviorSpec.bpmn
│ │ │ │ ├── DrilldownOverlaysBehaviorSpec.js
│ │ │ │ ├── DrilldownSpec.js
│ │ │ │ ├── collaboration-subprocesses.bpmn
│ │ │ │ ├── diagram-missing-plane.bpmn
│ │ │ │ ├── legacy-subprocesses.bpmn
│ │ │ │ ├── nested-subprocesses.bpmn
│ │ │ │ ├── plane-missing-bpmnelement.bpmn
│ │ │ │ ├── process-missing-bpmndiagram.bpmn
│ │ │ │ ├── subprocess-missing-bpmndiagram.bpmn
│ │ │ │ └── subprocess-missing-di.bpmn
│ │ │ ├── editor-actions/
│ │ │ │ └── BpmnEditorActionsSpec.js
│ │ │ ├── grid-snapping/
│ │ │ │ ├── BpmnGridSnapping.bpmn
│ │ │ │ ├── BpmnGridSnappingSpec.js
│ │ │ │ ├── basic.bpmn
│ │ │ │ └── behavior/
│ │ │ │ ├── AutoPlaceBehavior.bpmn
│ │ │ │ ├── AutoPlaceBehaviorSpec.js
│ │ │ │ ├── AutoResizeBehavior.bpmn
│ │ │ │ ├── AutoResizeBehaviorSpec.js
│ │ │ │ ├── CreateParticipantBehavior.bpmn
│ │ │ │ ├── CreateParticipantBehaviorSpec.js
│ │ │ │ ├── LayoutConnectionBehavior.bpmn
│ │ │ │ └── LayoutConnectionBehaviorSpec.js
│ │ │ ├── interaction-events/
│ │ │ │ └── BpmnInteractionEventsSpec.js
│ │ │ ├── keyboard/
│ │ │ │ └── BpmnKeyboardBindingsSpec.js
│ │ │ ├── keyboard-move-selection/
│ │ │ │ ├── KeyboardMoveSelectionSpec.js
│ │ │ │ └── keyboard-move-selection.bpmn
│ │ │ ├── label-editing/
│ │ │ │ ├── LabelEditing.bpmn
│ │ │ │ ├── LabelEditingPreviewSpec.js
│ │ │ │ └── LabelEditingProviderSpec.js
│ │ │ ├── label-link/
│ │ │ │ ├── LabelLink.bpmn
│ │ │ │ └── LabelLinkSpec.js
│ │ │ ├── modeling/
│ │ │ │ ├── AppendShapeSpec.js
│ │ │ │ ├── BendpointsSpec.js
│ │ │ │ ├── BpmnFactorySpec.js
│ │ │ │ ├── BpmnUpdater.bpmn
│ │ │ │ ├── BpmnUpdater.incompleteDi.bpmn
│ │ │ │ ├── BpmnUpdaterSpec.js
│ │ │ │ ├── CreateConnectionSpec.js
│ │ │ │ ├── DeleteConnectionSpec.js
│ │ │ │ ├── DeleteParticipantSpec.js
│ │ │ │ ├── DeleteShape.cropping.bpmn
│ │ │ │ ├── DeleteShapeSpec.js
│ │ │ │ ├── DropSpec.js
│ │ │ │ ├── ElementFactory.bpmn
│ │ │ │ ├── ElementFactorySpec.js
│ │ │ │ ├── IdClaim.bpmn
│ │ │ │ ├── IdClaimSpec.js
│ │ │ │ ├── LabelBoundsSpec.js
│ │ │ │ ├── LabelBoundsSpec.simple.bpmn
│ │ │ │ ├── LabelLayouting.initial.bpmn
│ │ │ │ ├── LabelLayouting.integration.bpmn
│ │ │ │ ├── LabelLayouting.move.bpmn
│ │ │ │ ├── LabelLayoutingSpec.js
│ │ │ │ ├── LoggingCroppingConnectionDocking.js
│ │ │ │ ├── MoveConnectionSpec.js
│ │ │ │ ├── MoveElements.boundary-connection.bpmn
│ │ │ │ ├── MoveElements.centered-connection.bpmn
│ │ │ │ ├── MoveElements.collaboration-association.bpmn
│ │ │ │ ├── MoveElements.data-input-output.bpmn
│ │ │ │ ├── MoveElements.eventBasedTargets.bpmn
│ │ │ │ ├── MoveElements.flow-collaboration.bpmn
│ │ │ │ ├── MoveElementsSpec.js
│ │ │ │ ├── MoveRulesSpec.js
│ │ │ │ ├── MoveShapeSpec.js
│ │ │ │ ├── MoveStress.bpmn
│ │ │ │ ├── MoveStressSpec.js
│ │ │ │ ├── ResizeShapeSpec.js
│ │ │ │ ├── SetColor.bpmn
│ │ │ │ ├── SetColorSpec.js
│ │ │ │ ├── UpdateAttachmentSpec.js
│ │ │ │ ├── UpdateLabel.bpmn
│ │ │ │ ├── UpdateLabelSpec.js
│ │ │ │ ├── UpdateModdleProperties.dataObject.bpmn
│ │ │ │ ├── UpdateModdleProperties.error.bpmn
│ │ │ │ ├── UpdateModdlePropertiesSpec.js
│ │ │ │ ├── UpdatePropertiesSpec.js
│ │ │ │ ├── UpdateSemanticParent.bpmn
│ │ │ │ ├── UpdateSemanticParentSpec.js
│ │ │ │ ├── append/
│ │ │ │ │ └── TextAnnotationSpec.js
│ │ │ │ ├── behavior/
│ │ │ │ │ ├── AdaptiveLabelPositioningBehavior.basics.bpmn
│ │ │ │ │ ├── AdaptiveLabelPositioningBehavior.boundary-events.bpmn
│ │ │ │ │ ├── AdaptiveLabelPositioningBehaviorSpec.js
│ │ │ │ │ ├── AssociationBehavior.bpmn
│ │ │ │ │ ├── AssociationBehaviorSpec.js
│ │ │ │ │ ├── AttachEventBehavior.bpmn
│ │ │ │ │ ├── AttachEventBehaviorSpec.js
│ │ │ │ │ ├── BoundaryEvent.bpmn
│ │ │ │ │ ├── BoundaryEventBehaviorSpec.js
│ │ │ │ │ ├── CompensateBoundaryEventBehavior.bpmn
│ │ │ │ │ ├── CompensateBoundaryEventBehaviorSpec.js
│ │ │ │ │ ├── CompensationAssociationBehavior.bpmn
│ │ │ │ │ ├── CompensationAssociationBehaviorSpec.js
│ │ │ │ │ ├── CreateBehavior.bpmn
│ │ │ │ │ ├── CreateBehaviorSpec.js
│ │ │ │ │ ├── CreateParticipantBehaviorSpec.js
│ │ │ │ │ ├── DataInputAssociationBehavior.bpmn
│ │ │ │ │ ├── DataInputAssociationBehaviorSpec.js
│ │ │ │ │ ├── DataObjectBehavior.create-data-association.bpmn
│ │ │ │ │ ├── DataObjectBehavior.data-object-reference.bpmn
│ │ │ │ │ ├── DataObjectBehavior.remove-data-association.bpmn
│ │ │ │ │ ├── DataObjectBehaviorSpec.js
│ │ │ │ │ ├── DataStoreBehavior.bpmn
│ │ │ │ │ ├── DataStoreBehavior.collaboration.bpmn
│ │ │ │ │ ├── DataStoreBehavior.connect.bpmn
│ │ │ │ │ ├── DataStoreBehavior.empty-pool.bpmn
│ │ │ │ │ ├── DataStoreBehavior.process.bpmn
│ │ │ │ │ ├── DataStoreBehavior.remove-participant.bpmn
│ │ │ │ │ ├── DataStoreBehaviorSpec.js
│ │ │ │ │ ├── DetachEventBehavior.bpmn
│ │ │ │ │ ├── DetachEventBehaviorSpec.js
│ │ │ │ │ ├── DropOnFlowBehavior.bpmn
│ │ │ │ │ ├── DropOnFlowBehaviorSpec.js
│ │ │ │ │ ├── EventBasedGatewayBehavior.bpmn
│ │ │ │ │ ├── EventBasedGatewayBehaviorSpec.js
│ │ │ │ │ ├── FixHoverBehavior.annotation.bpmn
│ │ │ │ │ ├── FixHoverBehavior.group.bpmn
│ │ │ │ │ ├── FixHoverBehavior.label.bpmn
│ │ │ │ │ ├── FixHoverBehavior.lane-connect.bpmn
│ │ │ │ │ ├── FixHoverBehavior.participant.bpmn
│ │ │ │ │ ├── FixHoverBehaviorSpec.js
│ │ │ │ │ ├── GroupBehaviorSpec.bpmn
│ │ │ │ │ ├── GroupBehaviorSpec.js
│ │ │ │ │ ├── ImportDockingFix.bpmn
│ │ │ │ │ ├── ImportDockingFixSpec.js
│ │ │ │ │ ├── IsHorizontalFix.bpmn
│ │ │ │ │ ├── IsHorizontalFixSpec.js
│ │ │ │ │ ├── LabelBehavior.bpmn
│ │ │ │ │ ├── LabelBehavior.copyPaste.bpmn
│ │ │ │ │ ├── LabelBehaviorSpec.js
│ │ │ │ │ ├── LayoutConnectionBehavior.bpmn
│ │ │ │ │ ├── LayoutConnectionBehaviorSpec.js
│ │ │ │ │ ├── MessageFlowBehavior.bpmn
│ │ │ │ │ ├── MessageFlowBehaviorSpec.js
│ │ │ │ │ ├── NonInterruptingBehavior.bpmn
│ │ │ │ │ ├── NonInterruptingBehaviorSpec.js
│ │ │ │ │ ├── ReconnectConnection.data-association.bpmn
│ │ │ │ │ ├── ReconnectConnectionSpec.js
│ │ │ │ │ ├── RemoveElementBehavior.bpmn
│ │ │ │ │ ├── RemoveElementBehavior.diagonal.bpmn
│ │ │ │ │ ├── RemoveElementBehavior.perpendicular.bpmn
│ │ │ │ │ ├── RemoveElementBehavior.vertical.diagonal.bpmn
│ │ │ │ │ ├── RemoveElementBehaviorSpec.js
│ │ │ │ │ ├── RemoveEmbeddedLabelBoundsBehavior.bpmn
│ │ │ │ │ ├── RemoveEmbeddedLabelBoundsBehaviorSpec.js
│ │ │ │ │ ├── RemoveParticipantBehaviorSpec.js
│ │ │ │ │ ├── ReplaceConnectionBehavior.association.bpmn
│ │ │ │ │ ├── ReplaceConnectionBehavior.boundary-events.bpmn
│ │ │ │ │ ├── ReplaceConnectionBehavior.message-sequence-flow.bpmn
│ │ │ │ │ ├── ReplaceConnectionBehaviorSpec.js
│ │ │ │ │ ├── ReplaceElementBehaviourSpec.js
│ │ │ │ │ ├── ResizeBehavior.lanes.bpmn
│ │ │ │ │ ├── ResizeBehavior.lanes.vertical.bpmn
│ │ │ │ │ ├── ResizeBehavior.participant.bpmn
│ │ │ │ │ ├── ResizeBehavior.participant.vertical.bpmn
│ │ │ │ │ ├── ResizeBehavior.subProcess.bpmn
│ │ │ │ │ ├── ResizeBehavior.textAnnotation.bpmn
│ │ │ │ │ ├── ResizeBehavior.utility.lanes-flowNodes.bpmn
│ │ │ │ │ ├── ResizeBehavior.utility.lanes.bpmn
│ │ │ │ │ ├── ResizeBehavior.utility.lanes.vertical-flowNodes.bpmn
│ │ │ │ │ ├── ResizeBehavior.utility.lanes.vertical.bpmn
│ │ │ │ │ ├── ResizeBehaviorSpec.js
│ │ │ │ │ ├── RootElementReferenceBehavior.bpmn
│ │ │ │ │ ├── RootElementReferenceBehaviorSpec.js
│ │ │ │ │ ├── SetCompensationActivityAfterPasteBehaviorSpec.bpmn
│ │ │ │ │ ├── SetCompensationActivityAfterPasteBehaviorSpec.js
│ │ │ │ │ ├── SpaceToolBehaviorSpec.group.bpmn
│ │ │ │ │ ├── SpaceToolBehaviorSpec.js
│ │ │ │ │ ├── SpaceToolBehaviorSpec.participant.bpmn
│ │ │ │ │ ├── SpaceToolBehaviorSpec.participant.vertical.bpmn
│ │ │ │ │ ├── SpaceToolBehaviorSpec.subprocess.bpmn
│ │ │ │ │ ├── SubProcessBehavior.copy-paste.bpmn
│ │ │ │ │ ├── SubProcessBehavior.multiple-planes.bpmn
│ │ │ │ │ ├── SubProcessBehavior.nested-subprocess-annotations.bpmn
│ │ │ │ │ ├── SubProcessBehavior.planes.bpmn
│ │ │ │ │ ├── SubProcessBehavior.start-event.bpmn
│ │ │ │ │ ├── SubProcessPlaneBehaviorSpec.js
│ │ │ │ │ ├── SubProcessStartEventBehaviorSpec.js
│ │ │ │ │ ├── TextAnnotationBehaviorSpec.bpmn
│ │ │ │ │ ├── TextAnnotationBehaviorSpec.js
│ │ │ │ │ ├── ToggleCollapseConnectionBehaviourSpec.bpmn
│ │ │ │ │ ├── ToggleCollapseConnectionBehaviourSpec.js
│ │ │ │ │ ├── ToggleElementCollapseBehaviour.bpmn
│ │ │ │ │ ├── ToggleElementCollapseBehaviourSpec.js
│ │ │ │ │ ├── UnclaimIdBehaviorSpec.bpmn
│ │ │ │ │ ├── UnclaimIdBehaviorSpec.js
│ │ │ │ │ ├── UnsetDefaultFlowBehaviorSpec.bpmn
│ │ │ │ │ ├── UnsetDefaultFlowBehaviorSpec.js
│ │ │ │ │ └── util/
│ │ │ │ │ ├── GeometricUtilSpec.js
│ │ │ │ │ ├── LabelLayoutUtilSpec.js
│ │ │ │ │ ├── LineAttachmentUtilSpec.js
│ │ │ │ │ ├── LineIntersectSpec.js
│ │ │ │ │ └── ResizeUtilSpec.js
│ │ │ │ ├── input-output/
│ │ │ │ │ └── DataInputOutput.bpmn
│ │ │ │ ├── lanes/
│ │ │ │ │ ├── AddLaneSpec.js
│ │ │ │ │ ├── DeleteLaneSpec.js
│ │ │ │ │ ├── ResizeLaneSpec.js
│ │ │ │ │ ├── SplitLane.nested.bpmn
│ │ │ │ │ ├── SplitLane.nested.vertical.bpmn
│ │ │ │ │ ├── SplitLaneSpec.js
│ │ │ │ │ ├── UpdateFlowNodeRefs.basic.bpmn
│ │ │ │ │ ├── UpdateFlowNodeRefsSpec.js
│ │ │ │ │ ├── lanes-flow-nodes-vertical.bpmn
│ │ │ │ │ ├── lanes-flow-nodes.bpmn
│ │ │ │ │ ├── lanes.bpmn
│ │ │ │ │ ├── lanes.only.bpmn
│ │ │ │ │ ├── lanes.only.vertical.bpmn
│ │ │ │ │ ├── lanes.vertical.bpmn
│ │ │ │ │ ├── participant-lane-vertical.bpmn
│ │ │ │ │ ├── participant-lane.bpmn
│ │ │ │ │ ├── participant-no-lane-vertical.bpmn
│ │ │ │ │ ├── participant-no-lane.bpmn
│ │ │ │ │ └── participant-single-lane.bpmn
│ │ │ │ └── layout/
│ │ │ │ ├── Helper.js
│ │ │ │ ├── LayoutAssociationSpec.js
│ │ │ │ ├── LayoutConnectionSpec.js
│ │ │ │ ├── LayoutDataAssociationSpec.js
│ │ │ │ ├── LayoutMessageFlowSpec.bpmn
│ │ │ │ ├── LayoutMessageFlowSpec.js
│ │ │ │ ├── LayoutMessageFlowSpec.vertical.bpmn
│ │ │ │ ├── LayoutSequenceFlowSpec.boundaryEvents.bpmn
│ │ │ │ ├── LayoutSequenceFlowSpec.boundaryEventsLoops.bpmn
│ │ │ │ ├── LayoutSequenceFlowSpec.flowElements.bpmn
│ │ │ │ ├── LayoutSequenceFlowSpec.js
│ │ │ │ ├── LayoutSequenceFlowSpec.subProcess.bpmn
│ │ │ │ ├── LayoutSequenceFlowSpec.vertical.boundaryEvents.bpmn
│ │ │ │ ├── LayoutSequenceFlowSpec.vertical.boundaryEventsLoops.bpmn
│ │ │ │ ├── LayoutSequenceFlowSpec.vertical.flowElements.bpmn
│ │ │ │ └── LayoutSequenceFlowSpec.vertical.subProcess.bpmn
│ │ │ ├── modeling-feedback/
│ │ │ │ ├── ModelingFeedback.bpmn
│ │ │ │ └── ModelingFeedbackSpec.js
│ │ │ ├── ordering/
│ │ │ │ ├── BpmnDiOrderingSpec.js
│ │ │ │ ├── BpmnOrderingProviderSpec.js
│ │ │ │ ├── Helper.js
│ │ │ │ ├── collapsed-subprocess.bpmn
│ │ │ │ ├── data-association.bpmn
│ │ │ │ ├── groups.bpmn
│ │ │ │ ├── ordering-start-event.bpmn
│ │ │ │ ├── ordering-subprocesses.bpmn
│ │ │ │ ├── ordering.bpmn
│ │ │ │ └── wrong-di-order.bpmn
│ │ │ ├── outline/
│ │ │ │ ├── OutlineProvider.bpmn
│ │ │ │ └── OutlineProviderSpec.js
│ │ │ ├── palette/
│ │ │ │ └── PaletteProviderSpec.js
│ │ │ ├── popup-menu/
│ │ │ │ ├── ReplaceMenuProvider.collapsedSubProcess.bpmn
│ │ │ │ ├── ReplaceMenuProvider.compensation-activity.bpmn
│ │ │ │ ├── ReplaceMenuProvider.conditionalFlows.bpmn
│ │ │ │ ├── ReplaceMenuProvider.defaultFlows.bpmn
│ │ │ │ ├── ReplaceMenuProvider.defaultFlowsFromComplexGateways.bpmn
│ │ │ │ ├── ReplaceMenuProvider.defaultFlowsFromInclusiveGateways.bpmn
│ │ │ │ ├── ReplaceMenuProvider.events.bpmn
│ │ │ │ ├── ReplaceMenuProvider.pools.bpmn
│ │ │ │ ├── ReplaceMenuProvider.subProcesses.bpmn
│ │ │ │ └── ReplaceMenuProviderSpec.js
│ │ │ ├── replace/
│ │ │ │ ├── BpmnReplace.collaboration.bpmn
│ │ │ │ ├── BpmnReplace.collaboration.vertical.bpmn
│ │ │ │ ├── BpmnReplace.collapsedSubProcess.bpmn
│ │ │ │ ├── BpmnReplace.compensation.bpmn
│ │ │ │ ├── BpmnReplace.dataObjects.bpmn
│ │ │ │ ├── BpmnReplace.eventSubProcesses.bpmn
│ │ │ │ ├── BpmnReplace.poolMessageFlows.bpmn
│ │ │ │ ├── BpmnReplace.poolMessageFlows.vertical.bpmn
│ │ │ │ ├── BpmnReplaceSpec.js
│ │ │ │ ├── ReplaceRulesSpec.js
│ │ │ │ └── SubProcess-collapsed.bpmn
│ │ │ ├── replace-preview/
│ │ │ │ ├── BpmnReplacePreview.bpmn
│ │ │ │ └── BpmnReplacePreviewSpec.js
│ │ │ ├── rules/
│ │ │ │ ├── BpmnRules.attaching.bpmn
│ │ │ │ ├── BpmnRules.boundaryEvent.bpmn
│ │ │ │ ├── BpmnRules.collaboration-dataAssociation.bpmn
│ │ │ │ ├── BpmnRules.collaboration-empty.bpmn
│ │ │ │ ├── BpmnRules.collaboration-lanes.bpmn
│ │ │ │ ├── BpmnRules.collaboration.bpmn
│ │ │ │ ├── BpmnRules.collapsedPools.bpmn
│ │ │ │ ├── BpmnRules.compensation-collaboration.bpmn
│ │ │ │ ├── BpmnRules.compensation.bpmn
│ │ │ │ ├── BpmnRules.connectOnCreate.bpmn
│ │ │ │ ├── BpmnRules.dataAssociation.bpmn
│ │ │ │ ├── BpmnRules.dataInputOutput.collaboration.bpmn
│ │ │ │ ├── BpmnRules.dataInputOutput.process.bpmn
│ │ │ │ ├── BpmnRules.detaching.bpmn
│ │ │ │ ├── BpmnRules.eventBasedGatewayBasic.bpmn
│ │ │ │ ├── BpmnRules.groups.bpmn
│ │ │ │ ├── BpmnRules.insert.bpmn
│ │ │ │ ├── BpmnRules.messageFlow.bpmn
│ │ │ │ ├── BpmnRules.moveLane.bpmn
│ │ │ │ ├── BpmnRules.multiSelectionPools.bpmn
│ │ │ │ ├── BpmnRules.process.bpmn
│ │ │ │ ├── BpmnRules.subProcess-dataAssociation.bpmn
│ │ │ │ ├── BpmnRulesSpec.js
│ │ │ │ └── Helper.js
│ │ │ ├── search/
│ │ │ │ ├── BpmnSearchProviderSpec.js
│ │ │ │ ├── bpmn-search-collaboration.bpmn
│ │ │ │ ├── bpmn-search-sorting.bpmn
│ │ │ │ └── bpmn-search.bpmn
│ │ │ ├── snapping/
│ │ │ │ ├── BpmnConnectSnapping.bpmn
│ │ │ │ ├── BpmnConnectSnappingSpec.js
│ │ │ │ ├── BpmnCreateMoveSnapping.boundary-events.bpmn
│ │ │ │ ├── BpmnCreateMoveSnapping.collaboration.bpmn
│ │ │ │ ├── BpmnCreateMoveSnapping.docking-create-mode.bpmn
│ │ │ │ ├── BpmnCreateMoveSnapping.docking-points.bpmn
│ │ │ │ ├── BpmnCreateMoveSnapping.process.bpmn
│ │ │ │ ├── BpmnCreateMoveSnapping.sequence-flows.bpmn
│ │ │ │ ├── BpmnCreateMoveSnapping.trbl-snapping.bpmn
│ │ │ │ └── BpmnCreateMoveSnappingSpec.js
│ │ │ └── space-tool/
│ │ │ ├── BpmnSpaceTool.artifacts.bpmn
│ │ │ ├── BpmnSpaceTool.basics.bpmn
│ │ │ ├── BpmnSpaceTool.boundary-events.bpmn
│ │ │ ├── BpmnSpaceTool.participants.bpmn
│ │ │ ├── BpmnSpaceTool.text-annotations.bpmn
│ │ │ └── BpmnSpaceToolSpec.js
│ │ ├── helper/
│ │ │ └── InjectSpec.js
│ │ ├── i18n/
│ │ │ ├── custom-translate/
│ │ │ │ ├── custom-translate.js
│ │ │ │ └── index.js
│ │ │ └── translateSpec.js
│ │ ├── import/
│ │ │ ├── BpmnTreeWalkerSpec.js
│ │ │ ├── ImporterSpec.js
│ │ │ ├── ModelWiringSpec.js
│ │ │ ├── data-association.bpmn
│ │ │ ├── elements/
│ │ │ │ ├── AssociationSpec.collaboration.bpmn
│ │ │ │ ├── AssociationSpec.compensation.bpmn
│ │ │ │ ├── AssociationSpec.data-association.bpmn
│ │ │ │ ├── AssociationSpec.data-input-output.bpmn
│ │ │ │ ├── AssociationSpec.events.bpmn
│ │ │ │ ├── AssociationSpec.js
│ │ │ │ ├── AssociationSpec.text-annotation.bpmn
│ │ │ │ ├── CollapsedSpec.js
│ │ │ │ ├── DataInputOutput.bpmn
│ │ │ │ ├── DataInputOutputSpec.js
│ │ │ │ ├── Groups.bpmn
│ │ │ │ ├── GroupsSpec.js
│ │ │ │ └── LabelSpec.js
│ │ │ ├── lane-flowNodes.bpmn
│ │ │ ├── lane-missing-flowNodeRef.bpmn
│ │ │ ├── missing-di-plane-root-element.bpmn
│ │ │ ├── missing-di-plane.bpmn
│ │ │ ├── sequenceFlow-missingWaypoints.bpmn
│ │ │ └── sequenceFlow-ordering.bpmn
│ │ └── util/
│ │ ├── ModelUtilSpec.js
│ │ └── svgHelpersSpec.js
│ ├── testBundle.js
│ └── util/
│ ├── KeyEvents.js
│ ├── MockEvents.js
│ ├── custom-rules/
│ │ ├── CustomRules.js
│ │ └── index.js
│ └── svgHelpers.js
└── tsconfig.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/CONTRIBUTING.md
================================================
# How to Contribute
Great to see you! Help us out by [filing bugs or feature requests](#work-with-issues), assisting others [in our forums](https://forum.bpmn.io/), or [contributing improvements](#contribute-improvements).
## Table of Contents
* [Work with Issues](#work-with-issues)
* [Create an Issue](#create-an-issue)
* [Help Out](#help-out)
* [Contribute Improvements](#contribute-improvements)
* [Setup the Project](#setup-the-project)
* [Build and Run the Project](#build-and-run-the-project)
* [Discuss Code Changes](#discuss-code-changes)
* [Adhere to the Unit Test Style](#adhere-to-the-unit-test-style)
* [Create a Pull Request](#create-a-pull-request)
## Work with Issues
We use our [issue tracker](https://github.com/bpmn-io/bpmn-js/issues) for project communication, discussion, and planning.
### Create an Issue
File bug reports or feature requests via [our issue tracker](https://github.com/bpmn-io/bpmn-js/issues/new/choose). Please mind the existing issue templates. These guide you and ensure you provide the details needed for us to follow up on your issue.
### Help Out
* Share your perspective on issues
* Be helpful and respect others when commenting
## Contribute Improvements
Learn how to set up the project locally, make changes, and contribute bug fixes and new features through pull requests.
### Setup the Project
The project development runs on top of the [diagram-js](https://github.com/bpmn-io/diagram-js) `develop` branch. The following code snippet sets up both libraries linking diagram-js to bpmn-js.
```sh
mkdir bpmn.io
cd bpmn.io
git clone git@github.com:bpmn-io/diagram-js.git -b develop
(cd diagram-js && npm i)
git clone git@github.com:bpmn-io/bpmn-js.git
(cd bpmn-js && npm install && npm link ../diagram-js)
```
For details consult our in depth [setup guide](../docs/project/SETUP.md).
### Build and Run the Project
Spin up a single modeler instance for local inspection:
```sh
npm start
```
Spin up the development environment, re-run tests with every file change:
```sh
npm run dev
```
You may also run against different browsers:
```sh
TEST_BROWSERS=Firefox npm run dev
```
Build, lint, and test the project, just as the CI does.
```sh
npm run all
```
### Discuss Code Changes
Create a [pull request](#create-a-pull-request) if you would like to have an in-depth discussion about some piece of code.
### Adhere to the Unit Test Style
In order to retrieve a sign-off for your contribution, it needs to be sufficiently and well tested. Please structure your unit tests into __given__, __when__ and __then__ ([ModelerSpec example](https://github.com/bpmn-io/bpmn-js/blob/develop/test/spec/ModelerSpec.js#L116), [ResizeBehaviorSpec example](https://github.com/bpmn-io/bpmn-js/blob/develop/test/spec/features/modeling/behavior/ResizeBehaviorSpec.js#L38)). To increase overall readability and understandability please also leave two empty lines before `describe(...)`, `it(...)` or _setup_ blocks on the same indentation level ([ModelerSpec example](https://github.com/bpmn-io/bpmn-js/blob/develop/test/spec/ModelerSpec.js#L49), [ResizeBehaviorSpec example](https://github.com/bpmn-io/bpmn-js/blob/develop/test/spec/features/modeling/behavior/ResizeBehaviorSpec.js#L36)).
### Create a Pull Request
We use pull requests for feature additions and bug fixes. If you are not yet familiar with pull requests, [read this excellent guide](https://gun.io/blog/how-to-github-fork-branch-and-pull-request).
Some things that make it easier for us to accept your pull requests
* The code adheres to our conventions
* spaces instead of tabs
* single-quotes
* ...
* The code is tested
* The `npm run all` build passes (executes tests + linting)
* The work is combined into a single commit
* The commit messages adhere to the [conventional commits guidelines](https://www.conventionalcommits.org)
We'd be glad to assist you if you do not get these things right in the first place.
---
Thanks for your interest in our library.
:heart: from the bpmn.io team.
================================================
FILE: .github/ISSUE_TEMPLATE/BUG_REPORT.md
================================================
---
name: Bug report
about: Report a problem and help us fix it.
labels: "bug"
---
### Describe the Bug
### Steps to Reproduce
1. do this
2. do that
### Expected Behavior
### Environment
- Browser: [e.g. IE 11, Chrome 69]
- OS: [e.g. Windows 7]
- Library version: [e.g. 2.0.0]
================================================
FILE: .github/ISSUE_TEMPLATE/FEATURE_REQUEST.md
================================================
---
name: Feature request
about: Suggest an idea or general improvement.
labels: "enhancement"
---
### Is your feature request related to a problem? Please describe.
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
### Describe the solution you'd like
A clear and concise description of what you want to happen.
### Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered.
### Additional context
Add any other context or screenshots about the feature request here.
================================================
FILE: .github/ISSUE_TEMPLATE/TASK.md
================================================
---
name: Task
about: Describe a generic activity we should carry out.
---
### What should we do?
### Why should we do it?
================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false
contact_links:
- name: Ask a question
url: https://forum.bpmn.io
about: Head over to our community forum to ask questions and get answers.
================================================
FILE: .github/merge-me.yml
================================================
reviewTeams:
- modeling-dev
- modeling-design
================================================
FILE: .github/workflows/CI.yml
================================================
name: CI
on: [ push, pull_request ]
jobs:
build_browsers:
strategy:
fail-fast: false
matrix:
os: [ ubuntu-latest ]
browser: [ Firefox, ChromeHeadless ]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Use Node.js
uses: actions/setup-node@v6
with:
node-version: 24
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Setup project
uses: bpmn-io/actions/setup@latest
- name: Build (with coverage)
if: matrix.browser == 'ChromeHeadless'
env:
COVERAGE: 1
TEST_BROWSERS: ${{ matrix.browser }}
run: npm run all
- name: Build
if: matrix.browser == 'Firefox'
env:
TEST_BROWSERS: ${{ matrix.browser }}
run: xvfb-run npm run all
- name: Upload coverage
if: matrix.browser == 'ChromeHeadless'
uses: codecov/codecov-action@v5
with:
fail_ci_if_error: true
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
build_os:
strategy:
fail-fast: false
matrix:
os: [ macos-latest, windows-latest ]
browser: [ ChromeHeadless ]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Use Node.js
uses: actions/setup-node@v6
with:
node-version: 24
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Setup project
uses: bpmn-io/actions/setup@latest
- name: Build
env:
TEST_BROWSERS: ${{ matrix.browser }}
run: npm run all
================================================
FILE: .github/workflows/CODE_SCANNING.yml
================================================
name: "Code Scanning"
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main, develop ]
paths-ignore:
- '**/*.md'
jobs:
codeql-build:
# CodeQL runs on ubuntu-latest, windows-latest, and macos-latest
runs-on: ubuntu-latest
permissions:
# required for all workflows
security-events: write
steps:
- name: Checkout repository
uses: actions/checkout@v6
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v4
with:
languages: javascript
config: |
paths-ignore:
- '**/test'
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4
================================================
FILE: .github/workflows/COMMENT_TARGETS_MAIN.yml
================================================
name: COMMENT_TARGETS_MAIN
on:
pull_request:
types:
- opened
branches:
- main
permissions:
pull-requests: write
jobs:
comment:
name: Comment on targeting main branch
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- name: Create comment
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
NUMBER: ${{ github.event.number }}
BODY: |
This pull request targets the `main` branch. Please target `main` for bug fixes only. Target `develop` for regular feature development.
run: gh issue comment "$NUMBER" --body "$BODY"
================================================
FILE: .github/workflows/MERGE_MAIN_TO_DEVELOP.yml
================================================
name: MERGE_MAIN_TO_DEVELOP
on:
push:
branches:
- "main"
jobs:
merge_main_to_develop:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout develop
uses: actions/checkout@v6
with:
token: ${{ secrets.BPMN_IO_TOKEN }}
persist-credentials: true
ref: develop
fetch-depth: 0
- name: Merge main to develop and push
run: |
git config user.name '${{ secrets.BPMN_IO_USERNAME }}'
git config user.email '${{ secrets.BPMN_IO_EMAIL }}'
git merge -m 'chore: merge main to develop' --no-edit origin/main
git push
- name: Notify failure on Slack
if: failure()
uses: slackapi/slack-github-action@v3
with:
method: chat.postMessage
token: ${{ secrets.SLACK_BOT_TOKEN }}
payload: |
channel: ${{ secrets.SLACK_CHANNEL_ID }}
text: "Automatic merge of to failed."
================================================
FILE: .github/workflows/POST_RELEASE.yml
================================================
name: POST_RELEASE
on:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+'
jobs:
post_release:
strategy:
matrix:
os: [ ubuntu-latest ]
node-version: [ 20 ]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Use Node.js
uses: actions/setup-node@v6
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- name: Setup project
uses: bpmn-io/actions/setup@latest
- name: Set TAG
run: echo "TAG=$(git describe --tags --abbrev=0)" >> $GITHUB_ENV
- name: Wait for published
env:
PKG: 'bpmn-js@${{ env.TAG }}'
run: tasks/stages/await-published
- name: Update integration test
env:
BPMN_IO_TOKEN: ${{ secrets.BPMN_IO_TOKEN }}
BPMN_IO_EMAIL: ${{ secrets.BPMN_IO_EMAIL }}
BPMN_IO_USERNAME: ${{ secrets.BPMN_IO_USERNAME }}
run: tasks/stages/update-integration-test
- name: Update demo
env:
BPMN_IO_TOKEN: ${{ secrets.BPMN_IO_TOKEN }}
BPMN_IO_EMAIL: ${{ secrets.BPMN_IO_EMAIL }}
BPMN_IO_USERNAME: ${{ secrets.BPMN_IO_USERNAME }}
BPMN_IO_DEMO_ENDPOINT: ${{ secrets.BPMN_IO_DEMO_ENDPOINT }}
run: tasks/stages/update-demo
- name: Update examples
env:
BPMN_IO_TOKEN: ${{ secrets.BPMN_IO_TOKEN }}
BPMN_IO_EMAIL: ${{ secrets.BPMN_IO_EMAIL }}
BPMN_IO_USERNAME: ${{ secrets.BPMN_IO_USERNAME }}
run: tasks/stages/update-examples
- name: Update website
env:
BPMN_IO_TOKEN: ${{ secrets.BPMN_IO_TOKEN }}
BPMN_IO_EMAIL: ${{ secrets.BPMN_IO_EMAIL }}
BPMN_IO_USERNAME: ${{ secrets.BPMN_IO_USERNAME }}
run: tasks/stages/update-website
- name: Update translations
env:
GITHUB_TOKEN: ${{ secrets.BPMN_IO_TOKEN }}
BPMN_IO_EMAIL: ${{ secrets.BPMN_IO_EMAIL }}
BPMN_IO_USERNAME: ${{ secrets.BPMN_IO_USERNAME }}
REVIEWERS: 'bpmn-io/modeling-dev'
TAG: ${{ env.TAG }}
run: tasks/stages/update-translations
================================================
FILE: .github/workflows/RELEASE.yml
================================================
name: RELEASE
on:
push:
branches:
- main
- develop
jobs:
release_please:
outputs:
release_created: ${{ steps.release.outputs.release_created }}
tag: ${{ steps.release.outputs.tag_name }}
permissions:
contents: write # to create release commit (google-github-actions/release-please-action)
pull-requests: write # to create release PR (google-github-actions/release-please-action)
runs-on: ubuntu-latest
steps:
- uses: google-github-actions/release-please-action@v4
id: release
with:
token: ${{ secrets.GITHUB_TOKEN }}
target-branch: main
release-type: node
publish:
needs: release_please
if: ${{ needs.release_please.outputs.release_created }}
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Use Node.js
uses: actions/setup-node@v6
with:
node-version: 24
cache: 'npm'
registry-url: 'https://registry.npmjs.org'
- name: Install dependencies
run: npm ci
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
================================================
FILE: .gitignore
================================================
node_modules/
dist/
coverage/
lib/**/*.d.ts
.idea
*.iml
.DS_Store
================================================
FILE: .release-please-manifest.json
================================================
{
"pull-request-title-pattern": "chore: release v${version}",
"changelog-sections": "[{\"type\":\"feat\",\"section\":\"Features\",\"hidden\":false},{\"type\":\"fix\",\"section\":\"Bug Fixes\",\"hidden\":false},{\"type\":\"deps\",\"section\":\"Dependency Updates\",\"hidden\":false}]",
"changelog-type": "github",
".": "18.13.0"
}
================================================
FILE: CHANGELOG.md
================================================
# Changelog
All notable changes to [bpmn-js](https://github.com/bpmn-io/bpmn-js) are documented here. We use [semantic versioning](http://semver.org/) for releases.
## Unreleased
___Note:__ Yet to be released changes appear here._
* `FIX`: disable grouping in popup menu during search ([bpmn-io/diagram-js#1014](https://github.com/bpmn-io/diagram-js/pull/1014))
* `FIX`: correct handling of annotations during sub-process collapse/expand, copy/paste, and remove actions ([#2388](https://github.com/bpmn-io/bpmn-js/pull/2388))
* `FIX`: allow undo of pasted sub-process ([#2388](https://github.com/bpmn-io/bpmn-js/pull/2388), [#2269](https://github.com/bpmn-io/bpmn-js/issues/2269))
* `DEPS`: update to `diagram-js@15.10.0`
## 18.13.1
* `FIX`: correct sequence flow layout for corner boundary events whose target is strictly axis-aligned ([#2270](https://github.com/bpmn-io/bpmn-js/issues/2270))
## 18.13.0
* `FEAT`: allow to create child elements from the context pad ([#2391](https://github.com/bpmn-io/bpmn-js/issues/2391))
## 18.12.1
* `FIX`: correctly replace non-interrupting event with an interrupting one ([#2313](https://github.com/bpmn-io/bpmn-js/issues/2313))
## 18.12.0
* `FEAT`: activate wheel zoom/scoll on `mouseover` ([#1008](https://github.com/bpmn-io/diagram-js/pull/1008))
* `FEAT`: prevent keyboard movement for boundary events without host ([#2386](https://github.com/bpmn-io/bpmn-js/pull/2386))
* `FIX`: prevent accidental creation of intermediate events during keyboard move ([#1803](https://github.com/bpmn-io/bpmn-js/issues/1803), [#1876](https://github.com/bpmn-io/bpmn-js/issues/1876))
* `DEPS`: update to `diagram-js@15.9.0`
## 18.11.0
* `FEAT`: add `cut` action and keyboard shortcut ([bpmn-io/diagram-js#1006](https://github.com/bpmn-io/diagram-js/pull/1006))
* `DEPS`: update to `diagram-js@15.7.0`
* `DEPS`: update to `bpmn-moddle@10.0.0`
* `DEPS`: update to `min-dash@5.0.0`
* `DEPS`: update to `ids@3.0.0`
* `DEPS`: update to `tiny-svg@4.1.4`
* `DEPS`: update to `diagram-js-direct-editing@3.3.0`
* `DEPS`: update to `min-dom@5.2.0`
## 18.10.1
* `DEPS`: update to `min-dash@4.2.3`
* `DEPS`: update to `tiny-svg@3.1.3`
## 18.10.0
* `FEAT`: add ability to duplicate elements ([bpmn-io/diagram-js#998](https://github.com/bpmn-io/diagram-js/pull/998))
* `DEPS`: update to `diagram-js@15.5.0`
## 18.9.1
* `FIX`: only draw links for currently selected elements ([#2365](https://github.com/bpmn-io/bpmn-js/pull/2365))
## 18.9.0
* `FEAT`: visually link external label with its target ([#2328](https://github.com/bpmn-io/bpmn-js/pull/2328))
* `FEAT`: add support for labels to `OutlineProvider#getOutline`
* `FIX`: ensure `BpmnRenderer#getShapePath` returns correct path for labels
## 18.8.0
* `FEAT`: allow copying data object references and `isCollection` property ([#2348](https://github.com/bpmn-io/bpmn-js/pull/2348))
## 18.7.0
* `FEAT`: support disabled entries in popup menu ([bpmn-io/diagram-js#987](https://github.com/bpmn-io/diagram-js/pull/987))
* `DEPS`: update to `diagram-js@15.4.0`
## 18.6.5
* `FIX`: ensure popup menu keyboard navigation accounts for group order ([bpmn-io/diagram-js#989](https://github.com/bpmn-io/diagram-js/pull/989))
* `DEPS`: update to `diagram-js@15.3.1`
## 18.6.4
* `FIX`: revert `AdHocSubProcess#cancelRemainingInstances` default value removal ([bpmn-io/bpmn-moddle#132](https://github.com/bpmn-io/bpmn-moddle/pull/132))
* `DEPS`: update to `bpmn-moddle@9.0.4`
## 18.6.3
* `FIX`: remove `AdHocSubProcess#cancelRemainingInstances` default value ([bpmn-io/bpmn-moddle#131](https://github.com/bpmn-io/bpmn-moddle/issues/131))
* `DEPS`: update to `bpmn-moddle@9.0.3`
## 18.6.2
* `FIX`: center task markers ([#1995](https://github.com/bpmn-io/bpmn-js/issues/1995))
## 18.6.1
* `FIX`: copy error, escalation, message and signal references when copying elements ([#1906](https://github.com/bpmn-io/bpmn-js/issues/1906), [#2249](https://github.com/bpmn-io/bpmn-js/issues/2249), [#2301](https://github.com/bpmn-io/bpmn-js/pull/2301))
## 18.6.0
* `FEAT`: support searching through arrays in popup menu ([bpmn-io/diagram-js#970](https://github.com/bpmn-io/diagram-js/pull/970))
* `FEAT`: prioritize `search` over `description` when matching popup menu entries ([bpmn-io/diagram-js#963](https://github.com/bpmn-io/diagram-js/pull/963))
* `FEAT`: sort `search` terms across all keys ([bpmn-io/diagram-js#963](https://github.com/bpmn-io/diagram-js/pull/963))
* `FIX`: always select first search entry ([bpmn-io/diagram-js#967](https://github.com/bpmn-io/diagram-js/pull/967))
* `DEPS`: update to `diagram-js@15.3.0`
## 18.5.0
* `FEAT`: allow text annotations for message flows ([#2281](https://github.com/bpmn-io/bpmn-js/issues/2281))
## 18.4.0
* `FEAT`: render collapsed event subprocess icons ([#50](https://github.com/bpmn-io/bpmn-js/issues/50))
## 18.3.2
* `FIX`: remove default start event for ad-hoc subprocess ([#2295](https://github.com/bpmn-io/bpmn-js/issues/2295))
* `FIX`: show modeling feedback error for data objects ([#2290](https://github.com/bpmn-io/bpmn-js/pull/2290))
## 18.3.1
* `FIX`: move artifacts when a participant is resized by space tool ([#2285](https://github.com/bpmn-io/bpmn-js/pull/2285))
## 18.3.0
* `FEAT`: allow to replace between variants of typed events ([#2282](https://github.com/bpmn-io/bpmn-js/pull/2282))
## 18.2.0
* `FEAT`: add ad-hoc subprocess option to replace menu ([#2276](https://github.com/bpmn-io/bpmn-js/pull/2276))
## 18.1.2
* `FIX`: canvas `autoFocus` must explicitly be enabled ([bpmn-io/diagram-js#956](https://github.com/bpmn-io/diagram-js/pull/956))
* `FIX`: properly integrate `zoomscroll` with canvas focus ([bpmn-io/diagram-js#956](https://github.com/bpmn-io/diagram-js/pull/956))
* `FIX`: properly integrate `movecanvas` with canvas focus ([bpmn-io/diagram-js#956](https://github.com/bpmn-io/diagram-js/pull/956))
## 18.1.1
* `FIX`: adjust search to prioritize start of word and exact matches ([bpmn-io/diagram-js#953](https://github.com/bpmn-io/diagram-js/pull/953))
* `FIX`: ignore whitespace when searching ([bpmn-io/diagram-js#954](https://github.com/bpmn-io/diagram-js/pull/954))
## 18.1.0
* `FIX`: clear selection when opening search pad ([bpmn-io/diagram-js#947](https://github.com/bpmn-io/diagram-js/pull/947))
* `FIX`: correct dangling selection after search pad interaction ([bpmn-io/diagram-js#947](https://github.com/bpmn-io/diagram-js/pull/947))
* `DEPS`: update to `diagram-js@15.2.2`
## 18.0.0
* `FEAT`: remove `outline` from `Viewer` modules ([#2135](https://github.com/bpmn-io/bpmn-js/issues/2135))
* `FEAT`: make `Canvas` a focusable element ([bpmn-io/diagram-js#662](https://github.com/bpmn-io/diagram-js/pull/662))
* `FEAT`: implicit keyboard binding ([bpmn-io/diagram-js#662](https://github.com/bpmn-io/diagram-js/pull/662))
* `FEAT`: integrate with global `search` ([#2235](https://github.com/bpmn-io/bpmn-js/pull/2235))
* `FEAT`: integrate `popup-menu` with `search` ([bpmn-io/diagram-js#932](https://github.com/bpmn-io/diagram-js/pull/932))
* `FEAT`: recognize modern `search` tokens in `search-pad` ([bpmn-io/diagram-js#932](https://github.com/bpmn-io/diagram-js/pull/932))
* `FIX`: correctly handle duplicate entries and whitespace in `search` ([bpmn-io/diagram-js#932](https://github.com/bpmn-io/diagram-js/pull/932))
* `FIX`: find `search` terms across all keys ([bpmn-io/diagram-js#932](https://github.com/bpmn-io/diagram-js/pull/932))
* `FIX`: `search` always returns tokens for matched items ([bpmn-io/diagram-js#932](https://github.com/bpmn-io/diagram-js/pull/932))
* `FIX`: prevent crash during label adjustment ([#2239](https://github.com/bpmn-io/bpmn-js/issues/2239))
* `FIX`: keep existing loop characteristics when toggling through the replace menu ([#2251](https://github.com/bpmn-io/bpmn-js/pull/2251))
* `FIX`: prevent covering multi selection with black box in `Viewer` ([#2135](https://github.com/bpmn-io/bpmn-js/issues/2135))
* `FIX`: generate types for main entry ([`986e2bb`](https://github.com/bpmn-io/bpmn-js/commit/986e2bb51ea301e6e0df56f3606a27424fb90179))
* `FIX`: correct handling of group name with whitespace only ([#2231](https://github.com/bpmn-io/bpmn-js/issues/2231))
* `DEPS`: update to `bpmn-moddle@9` ([#2114](https://github.com/bpmn-io/bpmn-js/pull/2114))
* `DEPS`: update to `diagram-js@15.1.0`
* `DEPS`: update to `diagram-js-direct-editing@3.2.0`
### Breaking Changes
* Require `Node >= 20`
* `Canvas` is now a focusable element and provides better support for native browser behaviors. Focus can be controlled with new `focus` and `restoreFocus` APIs ([bpmn-io/diagram-js#662](https://github.com/bpmn-io/diagram-js/pull/662)).
* Keyboard is now implicitly bound to canvas SVG element. Calls to `keyboard.bind` and `keyboard.bindTo` now result with a descriptive console error and have no effect ([bpmn-io/diagram-js#662](https://github.com/bpmn-io/diagram-js/pull/662)).
* Selection outline is no longer included in the viewer. If needed, add it as an additional module ([#2253](https://github.com/bpmn-io/bpmn-js/pull/2253)).
## 17.11.1
* `FIX`: handle searching elements without labels ([#2232](https://github.com/bpmn-io/bpmn-js/issues/2232), [#2234](https://github.com/bpmn-io/bpmn-js/pull/2234))
## 17.11.0
* `FEAT`: align search styles with other popups ([#2187](https://github.com/bpmn-io/bpmn-js/pull/2187))
* `FEAT`: prioritize start of tokens in search results ([#2187](https://github.com/bpmn-io/bpmn-js/pull/2187))
* `FIX`: do not commit viewport changes on `ESC` ([#2189](https://github.com/bpmn-io/bpmn-js/issues/2189), [#2187](https://github.com/bpmn-io/bpmn-js/pull/2187))
* `DEPS`: update to `diagram-js@14.10.0`
## 17.10.0
* `CHORE`: correct various type hints ([#2228](https://github.com/bpmn-io/bpmn-js/issues/2228))
* `FIX`: pasting compensation activity without boundary event ([#2070](https://github.com/bpmn-io/bpmn-js/issues/2070))
* `FIX`: lane resize constraints for se and nw direction ([#2209](https://github.com/bpmn-io/bpmn-js/issues/2209))
* `FIX`: auto place elements vertically in sub-processes ([#2127](https://github.com/bpmn-io/bpmn-js/issues/2127))
* `FIX`: hide lane label during direct editing
* `DEPS`: update to `diagram-js@14.9.0`
## 17.9.2
* `FIX`: keep direction when collapsing pools ([#2208](https://github.com/bpmn-io/bpmn-js/issues/2208))
## 17.9.1
* `FIX`: show delete action for labels ([#2163](https://github.com/bpmn-io/bpmn-js/issues/2163))
## 17.9.0
* `FIX`: remove incorrect attribute in replace menu ([#2196](https://github.com/bpmn-io/bpmn-js/pull/2196))
* `DEPS`: update to diagram-js@14.7.2
## 17.8.3
* `FIX`: add accessible label to drill down button ([#2194](https://github.com/bpmn-io/bpmn-js/pull/2194))
## 17.8.2
* `FIX`: do not suggest root elements in search ([#2143](https://github.com/bpmn-io/bpmn-js/issues/2143))
## 17.8.1
* `FIX`: gracefully handle missing process DI in drilldown ([#2180](https://github.com/bpmn-io/bpmn-js/pull/2180))
* `FIX`: do not cause HTML validation errors on move preview ([#2179](https://github.com/bpmn-io/bpmn-js/issues/2179))
* `DEPS`: update to `diagram-js@14.7.1`
## 17.8.0
* `FEAT`: keep global elements when deleting last participant ([#2175](https://github.com/bpmn-io/bpmn-js/pull/2175))
* `FIX`: allow undo after deleting last participants and data store ([#1676](https://github.com/bpmn-io/bpmn-js/issues/1676))
* `FIX`: allow styling markers with `canvas.addMarker` and css ([#2173](https://github.com/bpmn-io/bpmn-js/pull/2173))
* `CHORE`: render flow markers as part of `djs-visual` ([#2173](https://github.com/bpmn-io/bpmn-js/pull/2173))
* `DEPS`: update to `diagram-js@14.7.0`
## 17.7.1
* `FIX`: correct call activity outline ([#2167](https://github.com/bpmn-io/bpmn-js/issues/2167))
* `FIX`: gracefully handle missing `BPMNDiagram#plane` ([#2172](https://github.com/bpmn-io/bpmn-js/pull/2172), [#2171](https://github.com/bpmn-io/bpmn-js/pull/2171))
## 17.7.0
* `DEPS`: update to `diagram-js@14.6.0`
## 17.6.4
* `DEPS`: update to `diagram-js@14.5.4`
## 17.6.3
* `DEPS`: update to `diagram-js@14.5.3`
## 17.6.2
* `DEPS`: update to `diagram-js@14.5.2` ([#2158](https://github.com/bpmn-io/bpmn-js/pull/2158))
## 17.6.1
* `DEPS`: update to `diagram-js@14.5.1` ([#2157](https://github.com/bpmn-io/bpmn-js/pull/2157))
## 17.6.0
* `FEAT`: add ability to type services and events ([#2121](https://github.com/bpmn-io/bpmn-js/issues/2121), [#2153](https://github.com/bpmn-io/bpmn-js/pull/2153))
* `FIX`: remove preview on context pad close ([#2150](https://github.com/bpmn-io/bpmn-js/issues/2150))
* `FIX`: use tagged template in error logging ([#2151](https://github.com/bpmn-io/bpmn-js/pull/2151))
## 17.5.0
* `FEAT`: remove direct editing outline for embedded labels ([#2147](https://github.com/bpmn-io/bpmn-js/pull/2147))
* `FEAT`: do not translate technical errors ([#2145](https://github.com/bpmn-io/bpmn-js/pull/2145))
* `DEPS`: update to `diagram-js-direct-editing@3.0.1`
## 17.4.0
* `FEAT`: do not scale popup menu and context pad
* `DEPS`: update to `diagram-js@14.4.1`
## 17.3.0
* `FEAT`: auto-place elements vertically ([#2110](https://github.com/bpmn-io/bpmn-js/issues/2110))
## 17.2.2
* `FIX`: correct navigated viewer outline ([#2133](https://github.com/bpmn-io/bpmn-js/issues/2133))
## 17.2.1
* `FIX`: render popup menu on top
* `DEPS`: update to `diagram-js@14.3.1`
## 17.2.0
* `FEAT`: make popup menu keyboard navigatable
* `FIX`: address various accessibility issues
* `FIX`: correct various typing issues
* `DEPS`: update to `diagram-js@14.3.0`
* `DEPS`: update to `diagram-js-direct-editing@2.1.2`
## 17.1.0
* `FEAT`: handle splitting vertical lanes ([#2101](https://github.com/bpmn-io/bpmn-js/pull/2101))
## 17.0.2
* `FIX`: create hit boxes for vertical lanes ([#2093](https://github.com/bpmn-io/bpmn-js/issues/2093))
## 17.0.1
* `FIX`: fix rendering of gateway without marker ([#2102](https://github.com/bpmn-io/bpmn-js/pull/2102))
## 17.0.0
* `FEAT`: add to selection through SHIFT ([bpmn-io/diagram-js#796](https://github.com/bpmn-io/diagram-js/pull/851), [#2053](https://github.com/bpmn-io/bpmn-js/issues/2053))
* `CHORE`: remove broken touch interaction ([bpmn-io/diagram-js#796](https://github.com/bpmn-io/diagram-js/issues/796))
* `DEPS`: update to `diagram-js@14.0.0`
### Breaking Changes
* Migrated to `diagram-js@14` which removes touch interaction module, and dependency on unsupported `hammerjs` package. If you rely on touch interaction, you need to support touch interaction on your own.
## 16.5.0
* `FEAT`: handle adding vertical lanes ([#2086](https://github.com/bpmn-io/bpmn-js/issues/2086))
* `FIX`: don't fill multiple parallel events ([#2085](https://github.com/bpmn-io/bpmn-js/issues/2085))
## 16.4.0
* `FEAT`: handle resizing of vertical lanes ([#2062](https://github.com/bpmn-io/bpmn-js/issues/2062))
* `FEAT`: allow text annotations to overlap with the borders of subprocesses and pools ([#2049](https://github.com/bpmn-io/bpmn-js/issues/2049))
* `FEAT`: support modeling of gateway without marker ([#2063](https://github.com/bpmn-io/bpmn-js/issues/2063))
* `FIX`: correctly remove vertical lanes ([#2081](https://github.com/bpmn-io/bpmn-js/pull/2081))
* `FIX`: do not set label on planes ([#2033](https://github.com/bpmn-io/bpmn-js/issues/2033))
## 16.3.2
* `FIX`: support core replace in compensation behavior ([#2073](https://github.com/bpmn-io/bpmn-js/issues/2073))
## 16.3.1
* `FIX`: do not remove connection that is being created when pasting compensation boundary event and handler ([#2069](https://github.com/bpmn-io/bpmn-js/pull/2069))
## 16.3.0
* `FEAT`: improve handling of compensation association ([#2038](https://github.com/bpmn-io/bpmn-js/issues/2038))
## 16.2.0
* `DEPS`: update to `bpmn-moddle@8.1.0`
## 16.1.0
* `DEPS`: update to `diagram-js@13.4.0`
* `DEPS`: update to `diagram-js-direct-editing@2.1.1`
* `DEPS`: drop unused `object-refs` dependency
## 16.0.0
* `FEAT`: render vertical pools and lanes ([#2024](https://github.com/bpmn-io/bpmn-js/pull/2024))
* `FEAT`: sentence case titles and labels ([#2023](https://github.com/bpmn-io/bpmn-js/issues/2023))
* `FIX`: ensure all error translations are collected ([#2040](https://github.com/bpmn-io/bpmn-js/pull/2040))
* `DEPS` update to diagram-js@13.0.0
### Breaking Changes
* Major updates to [diagram-js@13](https://github.com/bpmn-io/diagram-js/blob/develop/CHANGELOG.md#1300) and [didi@10](https://github.com/nikku/didi/blob/main/CHANGELOG.md#1000). Make sure to check out the linked changelog updates.
* Multiple translation labels has been updated to sentence case. If you rely on the old casing, you need to update your translations.
## 15.2.2
* `FIX`: use correct types in BpmnRenderUtil ([#2036](https://github.com/bpmn-io/bpmn-js/pull/2036))
## 15.2.1
* `DEPS`: update to `diagram-js@13.8.1`
## 15.2.0
* `FEAT`: remove selection outline from connections ([diagram-js#826](https://github.com/bpmn-io/diagram-js/pull/826))
* `FEAT`: position context pad according to last waypoint for connections ([diagram-js#826](https://github.com/bpmn-io/diagram-js/pull/826))
* `FIX`: prevent access of non-existing connection bounds ([diagram-js#824](https://github.com/bpmn-io/diagram-js/pull/824))
* `FIX`: correct selection outline size for end event ([#2026](https://github.com/bpmn-io/bpmn-js/pull/2026))
* `DEPS`: update to `diagram-js@13.8.0`
## 15.1.3
* `FIX`: revert `djs-dragging` CSS class changes ([#2016](https://github.com/bpmn-io/bpmn-js/pull/2016))
* `FIX`: clear context pad hover timeout on close ([#2016](https://github.com/bpmn-io/bpmn-js/pull/2016))
* `DEPS`: update to `diagram-js@12.7.2`
## 15.1.2
* `FIX`: revert selection outline removal for connections ([#2011](https://github.com/bpmn-io/bpmn-js/pull/2011))
* `DEPS`: update to `diagram-js@12.7.1`
## 15.1.1
* `FIX`: adjust selection outline to external label ([#2001](https://github.com/bpmn-io/bpmn-js/issues/2001))
## 15.1.0
* `FEAT`: add toggle for non-interrupting events ([#2000](https://github.com/bpmn-io/bpmn-js/pull/2000))
* `FEAT`: keep events non-interrupting when using `bpmnReplace` by default ([#2000](https://github.com/bpmn-io/bpmn-js/pull/2000))
* `DEPS`: update to `diagram-js@12.7.0`
## 15.0.0
* `FEAT`: align selection outline with element's shape ([#1996](https://github.com/bpmn-io/bpmn-js/issues/1996))
* `FEAT`: preview append on hover ([#1985](https://github.com/bpmn-io/bpmn-js/pull/1985))
* `FEAT`: allow overriding `fill`, `stroke`, `width` and `height` when rendering elements ([#1985](https://github.com/bpmn-io/bpmn-js/pull/1985))
* `FIX`: renderer only renders actual flow elements ([#1985](https://github.com/bpmn-io/bpmn-js/pull/1985))
* `DEPS`: update to `diagram-js@12.6.0`
### Breaking Changes
* `BpmnRenderer` only renders actual flow elements (e.g. `bpmn:IntermediateCatchEvent` but not `bpmn:MessageEventDefinition`)
## 14.2.0
* `FEAT`: make spacetool local per default ([bpmn-io/diagram-js#811](https://github.com/bpmn-io/diagram-js/pull/811), [#1975](https://github.com/bpmn-io/bpmn-js/issues/1975))
* `FEAT`: add complex preview feature ([bpmn-io/diagram-js#807](https://github.com/bpmn-io/diagram-js/pull/807))
* `CHORE`: mark connection as dragging when moving bendpoint ([bpmn-io/diagram-js#807](https://github.com/bpmn-io/diagram-js/pull/807))
* `DEPS`: update to `diagram-js@12.5.0`
## 14.1.3
* `CHORE`: correctly output tag in [#1982](https://github.com/bpmn-io/bpmn-js/pull/1982)
## 14.1.2
* `CHORE`: fix POST_RELEASE job in [#1980](https://github.com/bpmn-io/bpmn-js/pull/1980)
## 14.1.1
* `FIX`: asset path by [__@nikku__](https://github.com/nikku) in [#1977](https://github.com/bpmn-io/bpmn-js/pull/1977)
## 14.1.0
* `FEAT`: ensure lanes aren't resized when using space tool in [#1972](https://github.com/bpmn-io/bpmn-js/pull/1972)
* `DOCS`: update translations for v14.0.0 by [__@bpmn-io-bot__](https://github.com/bpmn-io-bot) in [#1948](https://github.com/bpmn-io/bpmn-js/pull/1948)
## 14.0.0
* `FEAT`: do not hide overlays on canvas move per default ([diagram-js#798](https://github.com/bpmn-io/diagram-js/issues/798))
* `FEAT`: translate _Append TextAnnotation_ context pad action ([#1932](https://github.com/bpmn-io/bpmn-js/pull/1932))
* `FIX`: allow to create connection + event-based gateway ([#1490](https://github.com/bpmn-io/bpmn-js/issues/1490))
* `FIX`: make breadcrumb styling more robust ([#1945](https://github.com/bpmn-io/bpmn-js/pull/1945))
* `FIX`: correct copy of default sequence flow elements ([#1935](https://github.com/bpmn-io/bpmn-js/issues/1935))
* `CHORE`: extract `modeling-feedback` into dedicated module ([#1940](https://github.com/bpmn-io/bpmn-js/pull/1940))
* `CHORE`: drop deprecated callback support from public API
* `CHORE`: drop deprecated `import.parse.complete` event member `context`
* `DEPS`: update to `diagram-js@12.3.0`
* `DEPS`: update to `bpmn-moddle@8.0.1`
* `DEPS`: update to `ids@1.0.3`
### Breaking Changes
* Deprecated callback style API removed. Migrate to promise-based APIs, released with `v7.0.0`.
* Deprecated `import.parse.complete` event member `context` removed. Access the same information via the event itself, as released with `v7.0.0`.
## 13.2.2
* `FIX`: do not vertically resize empty pools using the space tool ([#1769](https://github.com/bpmn-io/bpmn-js/issues/1769))
## 13.2.1
* `FIX`: improve regular expression ([#1927](https://github.com/bpmn-io/bpmn-js/pull/1927))
* `FIX`: show non-interrupting event version in replace menu ([#1924](https://github.com/bpmn-io/bpmn-js/pull/1924))
## 13.2.0
* `CHORE`: provide align and distribute context pad and popup menu icons as html ([#1920](https://github.com/bpmn-io/bpmn-js/pull/1920))
* `DEPS`: update to `diagram-js@12.2.0`
## 13.1.0
* `FEAT`: allow event rendering without icons ([#1917](https://github.com/bpmn-io/bpmn-js/pull/1917))
## 13.0.9
* `CHORE`: update translations infra
## 13.0.8
_Republish of v13.0.7._
## 13.0.7
_Republish of v13.0.6._
## 13.0.6
* `DOCS`: update translations
## 13.0.5
* `DEPS`: update to `diagram-js@12.1.0`
## 13.0.4
* `DEPS`: bump to `diagram-js@12.0.2`
## 13.0.3
* `FIX`: update label on `modeling.updateModdleProperties` ([#1872](https://github.com/bpmn-io/bpmn-js/issues/1872))
## 13.0.2
* `FIX`: export types as `type` ([#1897](https://github.com/bpmn-io/bpmn-js/pull/1897))
* `DEPS`: bump to `diagram-js@12.0.1`
## 13.0.1
* `FIX`: correct some type definitions ([#1896](https://github.com/bpmn-io/bpmn-js/pull/1896))
## 13.0.0
* `FEAT`: rework and complete type definitions ([#1886](https://github.com/bpmn-io/bpmn-js/pull/1886))
* `DEPS`: update to `diagram-js@12.0.0`
## 12.1.1
* `DEPS`: update to `diagram-js@11.13.0`
## 12.1.0
* `FIX`: correct `Viewer#saveXML` type definition ([#1885](https://github.com/bpmn-io/bpmn-js/pull/1885))
* `FIX`: correct `Viewer` constructor type definition ([#1882](https://github.com/bpmn-io/bpmn-js/issues/1882))
## 12.0.0
* `FEAT`: move `create-append-anything` to [external module](https://github.com/bpmn-io/bpmn-js-create-append-anything) ([#1873](https://github.com/bpmn-io/bpmn-js/pull/1873), [#1862](https://github.com/bpmn-io/bpmn-js/issues/1862))
* `CHORE`: use `diagram-js@11.11.0` built-in selection after replace feature ([#1857](https://github.com/bpmn-io/bpmn-js/pull/1857))
* `DEPS`: update to `diagram-js@11.12.0`
### Breaking Changes
* The create/append anything features moved to an [external module](https://github.com/bpmn-io/bpmn-js-create-append-anything). Include it to restore the `v11` create/append behavior.
## 11.5.0
* `FEAT`: add root elements to definitions when provided via `modeling#update(Moddle)Properties`
## 11.4.1
* `FIX`: correct redo triggering on international keyboard layouts ([#1842](https://github.com/bpmn-io/bpmn-js/issues/1842))
## 11.4.0
* `FEAT`: translate append menu entry labels and groups ([#1810](https://github.com/bpmn-io/bpmn-js/pull/1810))
* `FEAT`: activate direct editing on participant creation ([#1845](https://github.com/bpmn-io/bpmn-js/pull/1845))
* `FIX`: dragging append menu entries creates element connection ([#1843](https://github.com/bpmn-io/bpmn-js/pull/1843))
* `FIX`: append shortcut triggers create menu if append not allowed ([#1840](https://github.com/bpmn-io/bpmn-js/issues/1840))
* `FIX`: restore marker rendering workaround ([`9c6e475`](https://github.com/bpmn-io/bpmn-js/commit/9c6e475681dd6b6a418b2fbc1dac19a9df360953))
## 11.3.1
_Republish of `v11.3.0`._
## 11.3.0
* `FEAT`: feature `service` and `user` tasks more prominently in replace menu ([#1836](https://github.com/bpmn-io/bpmn-js/pull/1836))
* `FEAT`: hide rare items initially from create/append menus ([#1836](https://github.com/bpmn-io/bpmn-js/pull/1836))
* `FEAT`: retrieve instantiation modules with context ([#1835](https://github.com/bpmn-io/bpmn-js/pull/1835))
* `DEPS`: update to `diagram-js@11.9.0`
## 11.2.0
_Adds create/append anything._
* `FEAT`: append menu available via context pad ([#1802](https://github.com/bpmn-io/bpmn-js/pull/1802), [#1809](https://github.com/bpmn-io/bpmn-js/pull/1809), [#1815](https://github.com/bpmn-io/bpmn-js/pull/1815), [#1818](https://github.com/bpmn-io/bpmn-js/pull/1818), [#1831](https://github.com/bpmn-io/bpmn-js/pull/1831))
* `FEAT`: create menu available via palette ([#1811](https://github.com/bpmn-io/bpmn-js/pull/1811), [#1809](https://github.com/bpmn-io/bpmn-js/pull/1809), [#1817](https://github.com/bpmn-io/bpmn-js/pull/1817))
* `FEAT`: simplify connection-multi icon ([#1822](https://github.com/bpmn-io/bpmn-js/pull/1822))
* `FEAT`: join paths `round` by default ([1827](https://github.com/bpmn-io/bpmn-js/pull/1827))
* `FEAT`: improved BPMN symbol rendering ([#1830](https://github.com/bpmn-io/bpmn-js/pull/1830))
* `FEAT`: round connection corners ([#1828](https://github.com/bpmn-io/bpmn-js/pull/1828))
* `FEAT`: improve visibility of popup menu ([#1812](https://github.com/bpmn-io/bpmn-js/issues/1812))
* `FIX`: missing special attributes in `bpmnElementFactory` ([#1807](https://github.com/bpmn-io/bpmn-js/pull/1807))
* `FIX`: handle `bpmn:DataObjectReference` without data object in replace menu ([#1823](https://github.com/bpmn-io/bpmn-js/pull/1823))
* `DEPS`: update to `diagram-js@11.8.0`
## 11.1.1
* `FIX`: correct popup menu display in fullscreen ([#1795](https://github.com/bpmn-io/bpmn-js/issues/1795))
* `DEPS`: update to `diagram-js@11.4.3`
## 11.1.0
* `FEAT`: add replace element keyboard binding ([#1785](https://github.com/bpmn-io/bpmn-js/pull/1785))
* `FEAT`: add `replaceElement` editor action ([#1785](https://github.com/bpmn-io/bpmn-js/pull/1785))
* `DEPS`: update to `diagram-js@11.4.1`
## 11.0.5
* `DEPS`: update to `diagram-js@11.3.0`
## 11.0.4
* `DEPS`: update to `diagram-js@11.2.0`
## 11.0.3
_Re-release of `v11.0.2`._
## 11.0.2
* `FIX`: correct test for replace options ([#1787](https://github.com/bpmn-io/bpmn-js/pull/1787))
## 11.0.1
* `DEPS`: update to `diagram-js@11.1.1`
## 11.0.0
_Reworks popup menu UI._
* `FEAT`: integrate new popup menu UI ([#1776](https://github.com/bpmn-io/bpmn-js/pull/1776))
* `DEPS`: update to `diagram-js@11.1.0` ([#1776](https://github.com/bpmn-io/bpmn-js/pull/1776))
### Breaking Changes
* New popup menu UI introduced with `diagram-js@11`. See [`diagram-js` breaking changes and migration guide](https://github.com/bpmn-io/diagram-js/blob/develop/CHANGELOG.md#breaking-changes).
* Keyboard-related features no longer use `KeyboardEvent#keyCode`. Use a polyfill (e.g. [keyboardevent-key-polyfill](https://www.npmjs.com/package/keyboardevent-key-polyfill)) if you need to support old browsers.
## 10.3.0
* `FEAT`: add BPMN specific space tool ([#1344](https://github.com/bpmn-io/bpmn-js/pull/1344))
* `FIX`: do not resize `bpmn:TextAnnotation` when using space tool ([#1344](https://github.com/bpmn-io/bpmn-js/pull/1344))
* `FIX`: correct attachers left hanging when using space tool ([#1344](https://github.com/bpmn-io/bpmn-js/pull/1344))
* `FIX`: stick labels to label targets when using space tool ([#1344](https://github.com/bpmn-io/bpmn-js/pull/1344), [#1302](https://github.com/bpmn-io/bpmn-js/issues/1302))
* `DEPS`: update to `diagram-js@10`
## 10.2.1
* `FIX`: correct preserving of outgoing connections on event-based gateway morph ([#1738](https://github.com/bpmn-io/bpmn-js/issues/1738))
## 10.2.0
* `DEPS`: update to `bpmn-moddle@8`
## 10.1.0
* `DEPS`: update to `diagram-js@9.1.0`
## 10.0.0
_Updates the library target to ES2018._
* `FEAT`: use ES2018 syntax ([#1737](https://github.com/bpmn-io/bpmn-js/pull/1737))
### Breaking Changes
* Migrated to ES2018 syntax. [Read the blog post with details and a migration guide](https://bpmn.io/blog/posts/2022-migration-to-es2018.html).
## 9.4.1
* `FIX`: ignore elements which cannot be colored ([#1734](https://github.com/bpmn-io/bpmn-js/pull/1734))
## 9.4.0
* `FEAT`: allow clipboard to be serialized ([#1707](https://github.com/bpmn-io/bpmn-js/pull/1707))
* `FEAT`: allow cloning of elements ([#1707](https://github.com/bpmn-io/bpmn-js/pull/1707))
* `FEAT`: copy groups in a safe manner ([#1707](https://github.com/bpmn-io/bpmn-js/pull/1707))
* `FIX`: make clipboard contents immutable ([#1707](https://github.com/bpmn-io/bpmn-js/pull/1707))
* `FIX`: do not alter inputs passed to `ElementFactory#create` ([#1711](https://github.com/bpmn-io/bpmn-js/pull/1711))
* `FIX`: prevent bogus meta-data to be attached on paste ([#1707](https://github.com/bpmn-io/bpmn-js/pull/1707))
* `FIX`: only claim existing IDs ([#1707](https://github.com/bpmn-io/bpmn-js/pull/1707))
* `FIX`: prevent double paste on label creation ([#1707](https://github.com/bpmn-io/bpmn-js/pull/1707))
* `FIX`: move labels when collapsing sub-process ([#1695](https://github.com/bpmn-io/bpmn-js/issues/1695))
* `FIX`: assign default size when expanding element ([#1687](https://github.com/bpmn-io/bpmn-js/issues/1687))
* `FIX`: render sequence flows always on top ([#1716](https://github.com/bpmn-io/bpmn-js/issues/1716))
* `DEPS`: update to `diagram-js@8.9.0`
* `DEPS`: update to `bpmn-moddle@7.1.3`
## 9.3.2
* `FIX`: prevent unnecessary scrollbar ([#1692](https://github.com/bpmn-io/bpmn-js/issues/1692))
* `FIX`: check for replacement using actual target ([#1699](https://github.com/bpmn-io/bpmn-js/pull/1699))
* `DEPS`: update to `diagram-js@8.7.1`
## 9.3.1
* `FIX`: properly size icons for distribute/align menu ([#1694](https://github.com/bpmn-io/bpmn-js/pull/1694))
## 9.3.0
* `FEAT`: add aligment and distribution menu ([#1680](https://github.com/bpmn-io/bpmn-js/issues/1680), [#1691](https://github.com/bpmn-io/bpmn-js/issues/1691))
* `DEPS`: update to `diagram-js@8.7.0`
## 9.2.2
* `FIX`: correctly toggle loop characteristics ([#1673](https://github.com/bpmn-io/bpmn-js/issues/1673))
## 9.2.1
* `FIX`: cancel direct editing before shape deletion ([#1677](https://github.com/bpmn-io/bpmn-js/issues/1677))
## 9.2.0
* `FEAT`: rework select and hover interaction on the diagram ([#1616](https://github.com/bpmn-io/bpmn-js/issues/1616), [#640](https://github.com/bpmn-io/diagram-js/pull/640), [#643](https://github.com/bpmn-io/diagram-js/pull/643))
* `FEAT`: rework diagram interaction handles ([#640](https://github.com/bpmn-io/diagram-js/pull/640))
* `FEAT`: clearly distinguish select and hover states ([#1616](https://github.com/bpmn-io/bpmn-js/issues/1616))
* `FEAT`: allow text annotation on sequence flows ([#1652](https://github.com/bpmn-io/bpmn-js/pull/1652))
* `FEAT`: add multi-element context pad ([#1525](https://github.com/bpmn-io/bpmn-js/pull/1525))
* `FEAT`: change default color to off black ([#1656](https://github.com/bpmn-io/bpmn-js/pull/1656))
* `FEAT`: select connection after connect ([#644](https://github.com/bpmn-io/diagram-js/pull/644))
* `FIX`: copy elements with `string` extension properties ([#1518](https://github.com/bpmn-io/bpmn-js/issues/1518))
* `FIX`: cancel direct editing before shape deletion ([#1664](https://github.com/bpmn-io/bpmn-js/issues/1664))
* `FIX`: remove connection on source connection deletion ([#1663](https://github.com/bpmn-io/bpmn-js/issues/1663))
* `FIX`: set correct label color when batch coloring elements ([#1653](https://github.com/bpmn-io/bpmn-js/issues/1653))
* `FIX`: always reconnect labels and associations ([#1659](https://github.com/bpmn-io/bpmn-js/pull/1659))
* `FIX`: correct connection drop highlighting
* `DEPS`: replace `inherits` with `inherits-browser`
* `DEPS`: bump to `diagram-js@8.5.0`
## 9.1.0
* `FEAT`: allow to select participant and subprocess via click on body ([#1646](https://github.com/bpmn-io/bpmn-js/pull/1646))
* `FIX`: comply with strict style-src CSP ([#1625](https://github.com/bpmn-io/bpmn-js/issues/1625))
* `FIX`: complete direct editing when selection changes ([#1648](https://github.com/bpmn-io/bpmn-js/pull/1648))
* `DEPS`: update to `diagram-js@8.3.0`
* `DEPS`: update to `min-dom@3.2.0`
## 9.0.4
* `FIX`: remove `label` property on empty label ([#1637](https://github.com/bpmn-io/bpmn-js/issues/1637))
* `FIX`: create drilldown overlays on `viewer.open` ([`574a67438`](https://github.com/bpmn-io/bpmn-js/commit/574a674381d6449b509396b6d17c4ca94674ea1c))
* `FIX`: render data association inside collapsed sub-processes ([#1619](https://github.com/bpmn-io/bpmn-js/issues/1619))
* `FIX`: preserve multi-instance properties when toggling between parallel and sequential ([#1581](https://github.com/bpmn-io/bpmn-js/issues/1581))
* `FIX`: correct hanging sequence flow label after collapsing sub-process ([#1617](https://github.com/bpmn-io/bpmn-js/issues/1617))
* `FIX`: correct start event not added to newly created sub-process ([#1631](https://github.com/bpmn-io/bpmn-js/issues/1631))
## 9.0.3
* `FIX`: submit direct editing result on drilldown ([#1609](https://github.com/bpmn-io/bpmn-js/issues/1609))
* `DEPS`: bump to `diagram-js@8.2.0` ([2bac149](https://github.com/bpmn-io/bpmn-js/commit/2bac1495058601fec203c134b41efe5600e5fc97))
## 9.0.2
* `FIX`: support modeling of groups in collapsed subporcesses ([#1606](https://github.com/bpmn-io/bpmn-js/issues/1606))
* `FIX`: override default padding of breadcrumb element ([#1608](https://github.com/bpmn-io/bpmn-js/pull/1608))
## 9.0.1
* `FIX`: use ES5 everywhere ([#1605](https://github.com/bpmn-io/bpmn-js/pull/1605))
* `FIX`: support DIs without associated business object ([#1605](https://github.com/bpmn-io/bpmn-js/pull/1605))
* `DEPS`: bump to `diagram-js@8.1.2` ([bdf9cf3](https://github.com/bpmn-io/bpmn-js/commit/bdf9cf3e752254a4c8172031d8a493955a9fca9c))
## 9.0.0
* `FEAT`: support drilldown and modeling of collapsed subprocesses ([#1443](https://github.com/bpmn-io/bpmn-js/issues/1443))
* `FEAT`: update embedded label bounds when shape is moved ([#1586](https://github.com/bpmn-io/bpmn-js/pull/1586))
* `FIX`: create di for embedded labels ([#1579](https://github.com/bpmn-io/bpmn-js/pull/1579))
* `CHORE`: expose `BpmnRenderer` extension points ([#1585](https://github.com/bpmn-io/bpmn-js/pull/1585))
* `DEPS`: bump to `diagram-js@8.1.1`
### Breaking Changes
* Reworked the link of elements to bpmn DIs. You must access the `di` directly from the diagram element instead of the `businessObject` [#1472](https://github.com/bpmn-io/bpmn-js/issues/1472).
* Reworked `viewer.open` behavior for single planes ([#1576](https://github.com/bpmn-io/bpmn-js/pull/1576)).
* Reworked import and `BpmnFactory` APIs [#1472](https://github.com/bpmn-io/bpmn-js/issues/1472).
* Added `bpmn-js.css`, which is required to display drilldown overlays correctly.
## 8.10.0
* `CHORE`: provide `ModelUtil#isAny` utility ([#1604](https://github.com/bpmn-io/bpmn-js/pull/1604))
* `CHORE`: provide `ModelUtil#getDi` utility ([#1604](https://github.com/bpmn-io/bpmn-js/pull/1604))
## 8.9.1
* `FIX`: re-use process for redo of first participant ([#1439](https://github.com/bpmn-io/bpmn-js/issues/1439))
* `FIX`: ensure IDs are claimed when used ([#1555](https://github.com/bpmn-io/bpmn-js/issues/1555))
* `FIX`: prevent morphing data stores outside participants ([#1508](https://github.com/bpmn-io/bpmn-js/issues/1508))
## 8.9.0
* `FEAT`: select newly created sub-process ([`6214772b`](https://github.com/bpmn-io/bpmn-js/commit/6214772b8519cb82f61c4867b16c112bc6344922))
* `FEAT`: select newly created group for immediate resizing ([`56eb34cc`](https://github.com/bpmn-io/bpmn-js/commit/56eb34cc826ca0dc8ee788575a504d5fda301292))
* `FEAT`: simplify color scheme
* `FIX`: set label color on `bpmndi:BPMNLabel#color` ([#1543](https://github.com/bpmn-io/bpmn-js/pull/1543))
* `FIX`: don't create illegal `bpmndi:BPMNEdge#waypoints` property ([#1544](https://github.com/bpmn-io/bpmn-js/issues/1544))
* `FIX`: correct direct editing on touch devices
* `DEPS`: update to `diagram-js@7.8.2`
## 8.8.3
* `FIX`: correct resize handles hidden behind element ([#1520](https://github.com/bpmn-io/bpmn-js/issues/1520))
* `FIX`: handle close to source or target drop on flow ([#1541](https://github.com/bpmn-io/bpmn-js/issues/1541))
* `CHORE`: bump to `diagram-js@7.6.3`
## 8.8.2
* `FIX`: properly re-use ID of a copied element if available ([#1503](https://github.com/bpmn-io/bpmn-js/pull/1509))
## 8.8.1
* `FIX`: re-use ID of a copied element if available ([#1503](https://github.com/bpmn-io/bpmn-js/pull/1503))
* `CHORE`: unbuild circular dependency with `ResizeUtil` ([#1500](https://github.com/bpmn-io/bpmn-js/pull/1500))
## 8.8.0
* `FEAT`: give `keyboard` fine-grained control over which events to handle ([#1493](https://github.com/bpmn-io/bpmn-js/issues/1493))
* `FIX`: correct keyboard shortcuts not working in direct editing mode ([#1493](https://github.com/bpmn-io/bpmn-js/issues/1493))
* `DEPS`: update to `diagram-js@7.15`
## 8.7.3
* `FIX`: convert file to `ES6` module ([#1478](https://github.com/bpmn-io/bpmn-js/pull/1478))
## 8.7.2
* `CHORE`: improve error recovery in ordering provider
* `DEPS`: update build dependencies
## 8.7.1
* `FIX`: allow connecting `bpmn:MessageFlow` to `bpmn:CallActivity` ([#1467](https://github.com/bpmn-io/bpmn-js/issues/1467))
* `DEPS`: update to `bpmn-moddle@7.1.2`
## 8.7.0
* `FEAT`: support BPMN in Color ([#1453](https://github.com/bpmn-io/bpmn-js/pull/1453))
* `DEPS`: update to `bpmn-moddle@7.1.1`
## 8.6.2
* `DEPS`: update diagram-js-direct-editing to v1.6.3
## 8.6.1
* `FIX`: serialize `bpmn:DataStoreReference` correctly in case if first participant is an empty pool ([#1456](https://github.com/bpmn-io/bpmn-js/issues/1456))
## 8.6.0
* `FEAT`: support Promise in `inject` test helper ([#1450](https://github.com/bpmn-io/bpmn-js/pull/1450))
* `DEPS`: update to `hosted-git@2.8.9` ([#1447](https://github.com/bpmn-io/bpmn-js/pull/1447))
## 8.5.0
* `FEAT`: reconnect message flows when participant is collapsed ([#1432](https://github.com/bpmn-io/bpmn-js/pull/1432))
* `FEAT`: replace elements on create ([#1340](https://github.com/bpmn-io/bpmn-js/issues/1340))
* `FEAT`: show message name on message flow ([#777](https://github.com/bpmn-io/bpmn-js/issues/777))
* `FEAT`: ensure auto-placed elements are visible
* `FIX`: fix reversed connection preview ([#1431](https://github.com/bpmn-io/bpmn-js/issues/1431))
* `FIX`: copy root element references on replace ([#1430](https://github.com/bpmn-io/bpmn-js/issues/1431))
* `DEPS`: update to `diagram-js@7.3.0`
## 8.4.0
* `FIX`: disallow inserting multiple elements on a sequence flow ([#1440](https://github.com/bpmn-io/bpmn-js/issues/1440))
## 8.3.1
* `FIX`: correctly serialize `xml` attributes on `Any` elements
* `DEPS`: update bump to `bpmn-moddle@7.0.5`
## 8.3.0
* `FEAT`: enable connection tool for text annotations ([#1428](https://github.com/bpmn-io/bpmn-js/pull/1428))
## 8.2.2
* `FIX`: always emit `saveXML.done`
* `FIX`: correct path intersections not being detected in certain cases
* `CHORE`: bump to `diagram-js@7.2.3`
## 8.2.1
* `FIX`: prevent bendpoint hover error ([#1387](https://github.com/bpmn-io/bpmn-js/issues/1387))
## 8.2.0
* `FIX`: correct label colors on connect / hover ([#1380](https://github.com/bpmn-io/bpmn-js/issues/1380))
* `FIX`: correct new parent indicator when leaving lane ([#1413](https://github.com/bpmn-io/bpmn-js/issues/1413))
* `CHORE`: update to `diagram-js@7.2.0`
## 8.1.0
* `TEST`: simplify markup created by built-in test helpers
## 8.0.1
* `FIX`: activate, not toggle global connect tool on palette click
* `FIX`: only allow cancel boundary events on transactions
* `CHORE`: add `npm start` script for demo purposes
## 8.0.0
* `FEAT`: improve replace label for collapsed pools ([`8faee2bd`](https://github.com/bpmn-io/bpmn-js/commit/8faee2bde9a74b75b4b6bb9b003507652e75c9c5))
* `FEAT`: allow participant multiplicity marker to be toggled ([#533](https://github.com/bpmn-io/bpmn-js/issues/533))
* `FEAT`: support soft breaks / discretionary hyphens in labels ([#1383](https://github.com/bpmn-io/bpmn-js/issues/1383))
* `FEAT`: improve tool activation via keyboard shortcuts or editor actions
* `FEAT`: allow components to react to auxiliary mouse button interactions
* `FEAT`: move canvas on auxiliary button mouse down
* `CHORE`: bump to `diagram-js@7`
### Breaking Changes
* Auxiliary mouse button events will now be passed as `element.*` mouse events to components. You must filter your event listeners to prevent reactions to these events ([`1063f7c1`](https://github.com/bpmn-io/diagram-js/commit/1063f7c18474096d3d7c9e400ce82a1bf762a157)).
## 7.5.0
* `FEAT`: update translatable strings ([#1364](https://github.com/bpmn-io/bpmn-js/pull/1364))
* `FEAT`: add collection marker to DataObjectReference ([#381](https://github.com/bpmn-io/bpmn-js/issues/381))
* `FEAT`: provide generic command for updating moddle properties ([#1376](https://github.com/bpmn-io/bpmn-js/pull/1376))
* `FEAT`: add switch between DataStoreReference and DataObjectReference in replace menu ([#1372](https://github.com/bpmn-io/bpmn-js/issues/1372))
* `FIX`: align collection and parallel instance markers style ([#1371](https://github.com/bpmn-io/bpmn-js/issues/1371))
## 7.4.2
* `FIX`: correctly emit out `element.event` after drop-on-flow ([#1366](https://github.com/bpmn-io/bpmn-js/issues/1366))
## 7.4.1
* `FIX`: correct keyboard zoom in key on international keyboard shortcuts ([#1362](https://github.com/bpmn-io/bpmn-js/issues/1362))
## 7.4.0
* `CHORE`: bump to `diagram-js@6.8.0`
* `CHORE`: migrate to `travis-ci.com`
## 7.3.1
* `CHORE`: bump to `diagram-js@6.7.1`
## 7.3.0
* `FEAT`: disallow typed start events inside non-event based sub processes ([#831](https://github.com/bpmn-io/bpmn-js/issues/831))
* `CHORE`: bump to `diagram-js@6.7.0`
## 7.2.1
* `FIX`: disallow boundary events as message flow targets ([#1300](https://github.com/bpmn-io/bpmn-js/issues/1300))
## 7.2.0
_Republish of `v7.1.0`._
## 7.1.0
* `FEAT`: allow annotating groups ([#1327](https://github.com/bpmn-io/bpmn-js/issues/1327))
## 7.0.1
* `FIX`: roundtrip default `xml` namespace ([#1319](https://github.com/bpmn-io/bpmn-js/issues/1319))
* `CHORE`: bump to `bpmn-moddle@7.0.3`
## 7.0.0
* `FEAT`: make import and export APIs awaitable ([#812](https://github.com/bpmn-io/bpmn-js/issues/812))
* `FEAT`: update watermark ([#1281](https://github.com/bpmn-io/bpmn-js/pull/1281))
* `CHORE`: deprecated `import.parse.complete` context payload ([`157aec6e`](https://github.com/bpmn-io/bpmn-js/commit/157aec6e))
* `CHORE`: clarify license terms ([`bc98a637`](https://github.com/bpmn-io/bpmn-js/commit/bc98a63712f6ac5c66d39f59bf93e296e59ad1e0))
* `CHORE`: bump to `bpmn-moddle@7.0.1`
### Breaking Changes
* The toolkit now requires the ES6 `Promise` to be present. To support IE11 you must polyfill it.
## 6.5.1
* `FIX`: correct namespaces being removed on diagram export ([#1310](https://github.com/bpmn-io/bpmn-js/issues/1310))
* `CHORE`: bump to `bpmn-moddle@6.0.6`
## 6.5.0
* `FEAT`: prefer straight layout for sub-process connections ([#1309](https://github.com/bpmn-io/bpmn-js/pull/1309))
* `FEAT`: move common auto-place feature to diagram-js, add BPMN-specific auto-place feature ([#1284](https://github.com/bpmn-io/bpmn-js/pull/1284))
* `CHORE`: make bpmn-font a development dependency ([`63045bdf`](https://github.com/bpmn-io/bpmn-js/commit/63045bdfa87b9f1989a2a7a509facbeb4616acda))
* `CHORE`: bump to `diagram-js@6.6.1`
## 6.4.2
* `CHORE`: bump to `bpmn-moddle@6.0.5`
## 6.4.1
* `FIX`: parse `>` in attribute names and body tag
* `CHORE`: bump to `bpmn-moddle@6.0.4`
## 6.4.0
* `FEAT`: serialize link events with an empty name ([#1296](https://github.com/bpmn-io/bpmn-js/issues/1296))
## 6.3.5
* `FIX`: correct accidental resizing of label target ([#1294](https://github.com/bpmn-io/bpmn-js/issues/1294))
## 6.3.4
* `FIX`: export BPMNDI in correct order ([#985](https://github.com/bpmn-io/bpmn-js/issues/985))
## 6.3.3
* `FIX`: resize empty text annotations
* `CHORE`: bump `min-dom` version
* `CHORE`: bump to `diagram-js@6.4.1`
## 6.3.2
* `FIX`: correctly move flows when adding lane ([#1287](https://github.com/bpmn-io/bpmn-js/pull/1287))
* `FIX`: restore semantic IDs for non flow nodes ([#1285](https://github.com/bpmn-io/bpmn-js/issues/1285))
## 6.3.1
* `FIX`: prevent editor crash in some strict execution environments ([#1283](https://github.com/bpmn-io/bpmn-js/pull/1283))
## 6.3.0
* `FEAT`: generate more generic IDs for new elements ([`035bb0c1`](https://github.com/bpmn-io/bpmn-js/commit/035bb0c1fd01adbaab8a340cb1075aa57736540d))
* `FEAT`: copy referenced root elements (message, signal, ...) ([`dc5a566e`](https://github.com/bpmn-io/bpmn-js/commit/dc5a566e107bc156505a40de3331b3832afc4b8d))
* `FEAT`: ensure minimum size when resizing elements with space tool ([`7ee304f4`](https://github.com/bpmn-io/bpmn-js/commit/7ee304f424d1c9db46633523165d25ca1fabba1b))
* `FIX`: correct interaction events inside `bpmn:Group` elements ([#1278](https://github.com/bpmn-io/bpmn-js/issues/1278))
* `FIX`: correct copy and paste of collapsed sub-processes ([#1270](https://github.com/bpmn-io/bpmn-js/issues/1270))
* `FIX`: correct various space tool related issues ([#1019](https://github.com/bpmn-io/bpmn-js/issues/1019), [#878](https://github.com/bpmn-io/bpmn-js/issues/878))
* `CHORE`: rework space tool
* `CHORE`: update to `diagram-js@6.4.0`
## 6.2.1
* `FIX`: correct serialization of `DataAssociation#assignment`
* `CHORE`: update to `bpmn-moddle@6.0.2`
## 6.2.0
* `FIX`: keep non-duplicate outgoing connection when dropping on flows ([#1263](https://github.com/bpmn-io/bpmn-js/issues/1263))
* `FIX`: properly reconnect message flows when collapsing participant
* `CHORE`: update to `diagram-js@6.3.0`
* `CHORE`: update to `bpmn-moddle@6.0.1`
## 6.1.2
* `FIX`: translate _Append ReceiveTask_
* `FIX`: allow associations where data associations are allowed, too ([`4a675b37`](https://github.com/bpmn-io/bpmn-js/commit/4a675b378027532db413186ea292daeac087285b))
* `FIX`: correct origin snapping on multi-element create ([`27fec8bd`](https://github.com/bpmn-io/bpmn-js/commit/27fec8bdf1c6236e7ca09b5721b74b1b45b45d39))
* `CHORE`: update to `diagram-js@6.2.2`
## 6.1.1
_Republish of `v6.1.0`._
## 6.1.0
* `FEAT`: copy signals, escalations and errors ([#1245](https://github.com/bpmn-io/bpmn-js/pull/1245))
* `FEAT`: provide base viewer / modeler distributions ([`bb94b206`](https://github.com/bpmn-io/bpmn-js/commit/bb94b206a7c9ab3b80e283d6513600a9591c437d))
* `FEAT`: add horizontal and vertical resize handles
* `FEAT`: improve connection cropping (bump to `path-intersection@2`)
* `FIX`: correctly mark elements as changed on `{shape|connection}.create` undo
* `FIX`: do not open replace menu after multi create ([#1255](https://github.com/bpmn-io/bpmn-js/pull/1255))
* `CHORE`: update to `diagram-js@6.2.0`
## 6.0.7
* `FIX`: disable waypoints-cropping after pasting connections ([`9f8a724e`](https://github.com/bpmn-io/bpmn-js/commit/9f8a724e9a3ff66bfce14e06ab38066189111a95))
## 6.0.6
* `FIX`: create nested lanes in the correct parent again ([#1256](https://github.com/bpmn-io/bpmn-js/issues/1256), [#1253](https://github.com/bpmn-io/bpmn-js/issues/1253), [#1254](https://github.com/bpmn-io/bpmn-js/issues/1254))
## 6.0.5
* `FIX`: only update `Lane#flownNodeRefs` once during paste ([`4455c3fc`](https://github.com/bpmn-io/bpmn-js/commit/4455c3fc35290e51220566fb6539a1efc4d3612f))
* `FIX`: do not adjust labels on paste ([`b2b607f5`](https://github.com/bpmn-io/bpmn-js/commit/b2b607f5582d3409c789d831a0896aaa55949899))
* `FIX`: do not snap connection waypoints on paste ([`d769e6dd`](https://github.com/bpmn-io/bpmn-js/commit/d769e6ddb0cb2dc8befb2e7b31682925089ba8f1))
## 6.0.4
* `FIX`: correctly fix hover on cleanup ([#1247](https://github.com/bpmn-io/bpmn-js/pull/1247))
## 6.0.3
* `FIX`: render colored BPMN groups ([#1246](https://github.com/bpmn-io/bpmn-js/pull/1246))
* `CHORE`: bump to `diagram-js@6.0.2`
## 6.0.2
* `CHORE`: bump `diagram-js-direct-editing` dependency
## 6.0.1
* `CHORE`: bump to `diagram-js@6.0.1`
## 6.0.0
* `FEAT`: rework (re-)connecting of shapes ([#427](https://github.com/bpmn-io/bpmn-js/pull/1230))
### Breaking Changes
Connecting and re-connecting shapes got reworked via [#427](https://github.com/bpmn-io/bpmn-js/pull/1230):
* The rules `connection.reconnectStart` and `connection.reconnectEnd` got replaced with `connection.reconnect` rule
* `BpmnLayouter#layoutConnection` waypoints can be specified via hint
## 5.1.2
* `FIX`: account for label pasting in label behavior ([#1227](https://github.com/bpmn-io/bpmn-js/issues/1227))
## 5.1.1
* `FIX`: re-select only existing elements when dragging is finished ([#1225](https://github.com/bpmn-io/bpmn-js/issues/1225))
* `FIX`: correctly hide nested children of a collapsed shape
* `CHORE`: bump to [`diagram-js@5.1.1`](https://github.com/bpmn-io/diagram-js/blob/develop/CHANGELOG.md#511)
## 5.1.0
* `FEAT`: adjust label position post creation ([`41c6af18`](https://github.com/bpmn-io/bpmn-js/commit/41c6af183014626a0f84e0bda0f8e39018f9151e))
* `FEAT`: copy and paste boundary events ([`2e27d743`](https://github.com/bpmn-io/bpmn-js/commit/2e27d7430642439e30806941d0df43018ca729eb))
* `FIX`: ordering after moving boundary events between hosts ([#1207](https://github.com/bpmn-io/bpmn-js/issues/1207))
* `FIX`: do not remove sequence flow condition on type change ([`b2900786`](https://github.com/bpmn-io/bpmn-js/commit/b290078600ae4e45e7c72bd37919732e3f8fcbea))
* `FIX`: do not remove default sequence flow on type change ([`37bcd070`](https://github.com/bpmn-io/bpmn-js/commit/37bcd070e8406a43a7316893c6b68debeaae5e26))
* `FIX`: do not duplicate flow node references ([`168a1493`](https://github.com/bpmn-io/bpmn-js/commit/168a1493b26c3059d2440a70f7aa5991745b51e5))
* `FIX`: ignore labels that are being created in adaptive label positioning ([`44cceb5d`](https://github.com/bpmn-io/bpmn-js/commit/44cceb5da287a0ad01d9389f475284c88eda7f7b))
## 5.0.5
* `FIX`: snap connections to task mid ([`86c61b0`](https://github.com/bpmn-io/bpmn-js/commit/86c61b0c0d6dcf776adda94b6d72b621644c2abe))
* `FIX`: snap connections to sub process mid ([`83e9f05`](https://github.com/bpmn-io/bpmn-js/commit/83e9f05efab6fbe57100e11d0443291a561bdfe4))
* `FIX`: complete direct editing when auto place starts ([`dcf440b`](https://github.com/bpmn-io/bpmn-js/commit/dcf440b07684339bdb52ba97cd1c83f9eb234044))
* `FIX`: do not clear diagram if no diagram to clear ([#1181](https://github.com/bpmn-io/bpmn-js/issues/1181))
* `FIX`: copy boundary events attachments ([#1190](https://github.com/bpmn-io/bpmn-js/issues/1190))
* `FIX`: do not copy generic properties ([`a74d83`](https://github.com/bpmn-io/bpmn-js/commit/a74d838dc78aceddf88e07231cf85a4cf9e0dd95))
## 5.0.4
* `FIX`: correct sequence flow layout after drop on flow ([#1178](https://github.com/bpmn-io/bpmn-js/issues/1178))
## 5.0.3
_Republish of `v5.0.2`._
## 5.0.2
* `FIX`: allow reconnecting to loops ([#1121](https://github.com/bpmn-io/bpmn-js/issues/1121))
* `CHORE`: bump to `diagram-js@5.0.1`
## 5.0.1
* `FIX`: import boundary event associations ([#1170](https://github.com/bpmn-io/bpmn-js/issues/1170))
## 5.0.0
* `FEAT`: add two-step copy and paste ([#1137](https://github.com/bpmn-io/bpmn-js/pull/1137))
* `FEAT` add `elements.create` rule for creating multiple elements ([#1137](https://github.com/bpmn-io/bpmn-js/pull/1137))
* `FEAT`: make containers draggable via their borders / labels only ([#1097](https://github.com/bpmn-io/bpmn-js/pull/1097), [#957](https://github.com/bpmn-io/bpmn-js/issues/957))
* `FEAT`: allow copied elements to be filtered ([#888](https://github.com/bpmn-io/bpmn-js/issues/888))
* `FIX`: prevent accidental dragging of participants and sub-processes ([#1097](https://github.com/bpmn-io/bpmn-js/pull/1097), [#957](https://github.com/bpmn-io/bpmn-js/issues/957))
* `FIX`: keep labels during pool extraction ([#921](https://github.com/bpmn-io/bpmn-js/issues/921))
* `FIX`: duplicate `bpmn:CategoryValue` when copying groups ([#1055](https://github.com/bpmn-io/bpmn-js/issues/1055))
* `FIX`: translate group creation entry in palette ([#1146](https://github.com/bpmn-io/bpmn-js/issues/1146))
* `CHORE`: use `element.copyProperty` event to copy category value when copying group ([`12bedca5`](https://github.com/bpmn-io/bpmn-js/pull/1137/commits/12bedca5ba2a05791591e53f554dc2310f6c1a6f))
* `CHORE`: bump to `diagram-js@5`
### Breaking Changes
Copy and paste as well as create is completely reworked:
* `CopyPaste`: remove `ModelCloneHelper` in favor of `ModdleCopy` service, remove `property.clone` event, add `moddleCopy.canCopyProperties`, `moddleCopy.canCopyProperty` and `moddleCopy.canSetCopiedProperty` event
* `BpmnRules`: removed `elements.paste` rule in favor of `elements.create` rule
* `BpmnRules`: removed `element.paste` rule
* `ElementFactory`: use `attrs.di` property instead of `attrs.colors` for fill and stroke when creating element through `ElementFactory#createBpmnElement`
* To prevent additional behavior on create after paste you should check for the `createElementsBehavior` hint, cf. [`bf180321`](https://github.com/bpmn-io/bpmn-js/commit/bf180321a3a40428c3f87b639b87cc3fc578066e#diff-2f0de25761fb7459e88071f83fd845c5R22)
## 4.0.4
* `FIX`: creating `bpmn:Participant` on single `bpmn:Group` throwing error ([#1133](https://github.com/bpmn-io/bpmn-js/issues/1133))
* `CHORE`: bump to `diagram-js@4.0.3`
## 4.0.3
* `FIX`: prevent dropping on labels and `bpmn:Group` elements ([#1131](https://github.com/bpmn-io/bpmn-js/pull/1131))
## 4.0.2
* `FIX`: correct element positioning update ([#1129](https://github.com/bpmn-io/bpmn-js/issues/1129))
* `CHORE`: bump to `diagram-js@4.0.2`
## 4.0.1
* `FIX`: prevent adding lane from crashing IE ([#746](https://github.com/bpmn-io/bpmn-js/issues/746))
* `FIX`: correct inverse space tool visuals ([#1105](https://github.com/bpmn-io/bpmn-js/issues/1105))
* `CHORE`: update `diagram-js-direct-editing` to prevent install warning
* `CHORE`: update to `diagram-js@4.0.1`
## 4.0.0
* `FEAT`: add top, right, bottom, left snapping with container elements ([#1108](https://github.com/bpmn-io/bpmn-js/pull/1108))
* `FEAT`: add grid snapping ([#987](https://github.com/bpmn-io/bpmn-js/pull/987))
* `FEAT`: allow modeling of groups ([#343](https://github.com/bpmn-io/bpmn-js/issues/343))
* `FEAT`: improve modeling rules behind event-based gateways ([#1006](https://github.com/bpmn-io/bpmn-js/pull/1006))
* `FEAT`: adjust default collapsed pool to standard height ([`5affe2570`](https://github.com/bpmn-io/bpmn-js/commit/5affe25705082937beace6b4a568f176a0527baf))
* `FEAT`: add connection previews ([#743](https://github.com/bpmn-io/bpmn-js/issues/743))
* `FEAT`: create expanded sub-process with start event included ([#1039](https://github.com/bpmn-io/bpmn-js/pull/1039))
* `FEAT`: improve automatic label adjustment for boundary events ([#1064](https://github.com/bpmn-io/bpmn-js/pull/1064))
* `FEAT`: improve creation of initial participant ([#1046](https://github.com/bpmn-io/bpmn-js/pull/1046))
* `FEAT`: improve boundary to host loop layout ([#1070](https://github.com/bpmn-io/bpmn-js/pull/1070))
* `FEAT`: make connection segment move the primary connection drag behavior
* `FEAT`: allow label and group movement everywhere ([#1080](https://github.com/bpmn-io/bpmn-js/pull/1080))
* `FEAT`: improve message flow to participant connection in the presence of lanes ([#950](https://github.com/bpmn-io/bpmn-js/issues/950))
* `FEAT`: allow detaching of boundary and attaching of intermediate events ([#1045](https://github.com/bpmn-io/bpmn-js/issues/1045))
* `FEAT`: simplify requested palette and context pad translations ([#1027](https://github.com/bpmn-io/bpmn-js/pull/1027))
* `FEAT`: simplify participant dragging in the presence of nested lanes ([`fdb299dc`](https://github.com/bpmn-io/bpmn-js/commit/fdb299dc888a7dcdb3f7674b6ed2a857864df457))
* `FEAT`: correctly render all kinds of multiple events ([#1091](https://github.com/bpmn-io/bpmn-js/pull/1091))
* `CHORE`: validate BPMN 2.0 XML ids as QNames ([`92c03679a`](https://github.com/bpmn-io/bpmn-js/commit/92c03679a4fd3c92a1c5ce3c97f7d366e2a5753a))
* `FIX`: correctly handle flow reconnection + type replacement ([#896](https://github.com/bpmn-io/bpmn-js/issues/896), [#1008](https://github.com/bpmn-io/bpmn-js/issues/1008))
### Breaking Changes
* `CHORE`: bump to [`diagram-js@4.0.0`](https://github.com/bpmn-io/diagram-js/blob/main/CHANGELOG.md#400)
## 3.5.0
* `FEAT`: restore `Viewer#importDefinitions` and make it public API ([#1112](https://github.com/bpmn-io/bpmn-js/pull/1112))
## 3.4.3
* `FIX`: prevent HTML injection in search ([diagram-js#362](https://github.com/bpmn-io/diagram-js/pull/362))
## 2.5.4
* `FIX`: prevent HTML injection in search ([diagram-js#362](https://github.com/bpmn-io/diagram-js/pull/362))
* `CHORE`: bump to `diagram-js@2.6.2`
## 3.4.2
* `FIX`: do not evaluate pasted text as HTML ([#1073](https://github.com/bpmn-io/bpmn-js/issues/1073))
## 2.5.3
* `FIX`: do not evaluate pasted text as HTML ([#1073](https://github.com/bpmn-io/bpmn-js/issues/1073))
## 3.4.1
_Republish of `v3.4.0` without `.git` folder._
## 3.4.0
* `FIX`: properly render colored connection markers ([#981](https://github.com/bpmn-io/bpmn-js/issues/981))
* `FEAT`: add ability to open different DI diagrams ([#87](https://github.com/bpmn-io/bpmn-js/issues/87))
* `FIX`: correctly layout straight boundary to target connections ([#891](https://github.com/bpmn-io/bpmn-js/issues/891))
* `FEAT`: resize participant to standard size on collapse ([#975](https://github.com/bpmn-io/bpmn-js/pull/975))
* `FEAT`: consistently layout connection on reconnect start and end ([#971](https://github.com/bpmn-io/bpmn-js/pull/971))
* `FEAT`: layout connection on element removal ([#989](https://github.com/bpmn-io/bpmn-js/issues/989))
* `FIX`: properly crop sequence flow ends on undo/redo ([#940](https://github.com/bpmn-io/bpmn-js/issues/940))
* `CHORE`: bump to [`diagram-js@3.3.0`](https://github.com/bpmn-io/diagram-js/blob/main/CHANGELOG.md#330)
## 3.3.1
* `FIX`: ignore unchanged direct editing completion
* `CHORE`: update to `diagram-js-direct-editing@1.4.2`
## 3.3.0
* `FEAT`: display `DataInput` / `DataOutput` labels ([`89719de3b`](https://github.com/bpmn-io/bpmn-js/commit/89719de3be50d9270227fd04216f7f19f0d018a2))
* `FEAT`: support basic `DataInput` / `DataOutput` move ([#962](https://github.com/bpmn-io/bpmn-js/pull/962))
* `FIX`: properly handle `DataInput` / `DataOutput` move ([#961](https://github.com/bpmn-io/bpmn-js/issues/961))
## 3.2.3
* `FIX`: update to `diagram-js-direct-editing@1.4.1` to trim trailing/leading whitespace in task names ([#763](https://github.com/bpmn-io/bpmn-js/issues/763))
## 3.2.2
* `FIX`: gracefully handle missing waypoints ([`45486f2`](https://github.com/bpmn-io/bpmn-js/commit/45486f2afe7f42fcac31be9ca477a7c94babe7d8))
## 3.2.1
* `FIX`: bump to `diagram-js@3.1.3` / `tiny-svg@2.2.1` to work around MS Edge bug ([`ed798a15`](https://github.com/bpmn-io/bpmn-js/commit/ed798a152539a613dbc9de9d61231ebbfb50987a))
## 3.2.0
* `FEAT`: set isHorizontal=true for new and updated participant/lane DIs ([#934](https://github.com/bpmn-io/bpmn-js/issues/934))
## 3.1.1
* `CHORE`: update to `diagram-js@3.1.1`
## 3.1.0
* `CHORE`: update to `diagram-js@3.1`
## 3.0.4
* `FIX`: render labels always on top ([#920](https://github.com/bpmn-io/bpmn-js/pull/920))
## 3.0.3
* `FIX`: do not join incoming/outgoing flows other than sequence flows on element deletion ([#917](https://github.com/bpmn-io/bpmn-js/issues/917))
## 3.0.2
* `FIX`: correct IE 11 delete keybinding ([#904](https://github.com/bpmn-io/bpmn-js/issues/904))
## 3.0.1
* `FIX`: restore copy-paste behavior
## 3.0.0
* `FEAT`: improve context pad tooltip titles for `EventBasedGateway` ([`350a5ab`](https://github.com/bpmn-io/bpmn-js/commit/350a5ab75ed675991599faff9615e4bbe184d491))
* `FEAT`: display group names ([#844](https://github.com/bpmn-io/bpmn-js/issues/844))
* `FEAT`: add ability to move selection with keyboard arrows ([#376](https://github.com/bpmn-io/bpmn-js/issues/376))
* `FEAT`: support `SHIFT` modifier to move elements / canvas with keyboard arrows at accelerated speed
* `FEAT`: require `Ctrl/Cmd` to be pressed as a modifier key to move the canvas via keyboard errors
* `FEAT`: auto-expand elements when children resize ([#786](https://github.com/bpmn-io/bpmn-js/issues/786))
* `CHORE`: bind editor actions and keyboard shortcuts for explicitly added features only ([#887](https://github.com/bpmn-io/bpmn-js/pull/887))
* `CHORE`: update to [`diagram-js@3.0.0`](https://github.com/bpmn-io/diagram-js/blob/main/CHANGELOG.md#300)
* `FIX`: disallow attaching of `BoundaryEvent` to a `ReceiveTask` following an `EventBasedGateway` ([#874](https://github.com/bpmn-io/bpmn-js/issues/874))
* `FIX`: fix date in license ([#882](https://github.com/bpmn-io/bpmn-js/pull/882))
### Breaking Changes
* `BpmnGlobalConnect` provider got removed. Use `connection.start` rule to customize whether connection should allowed to be started ([#565](https://github.com/bpmn-io/bpmn-js/issues/565), [#870](https://github.com/bpmn-io/bpmn-js/issues/870))
* `EditorActions` / `Keyboard` do not pull in features implicitly anymore. If you roll your own editor, include features you would like to ship with manually to provide the respective actions / keyboard bindings ([`645265ad`](https://github.com/bpmn-io/bpmn-js/commit/645265ad7e4a47e80657c671068a027752d7504f))
* Moving the canvas with keyboard arrows now requires the `Ctrl/Cmd` modifiers to be pressed.
## 2.5.2
* `FIX`: correct horizontal embedded label padding
## 2.5.1
* `FIX`: prevent error to be thrown on lane move ([#855](https://github.com/bpmn-io/bpmn-js/issues/855))
## 2.5.0
* `FEAT`: snap message flows to `bpmn:Event` center during connect ([#850](https://github.com/bpmn-io/bpmn-js/issues/850))
* `CHORE`: bump to `diagram-js@2.6.0`
* `FIX`: allow label movement over message flow ([#849](https://github.com/bpmn-io/bpmn-js/issues/849))
## 2.4.1
* `FIX`: make viewer IE 9 compatible
* `FIX`: prevent duplicate connections after drop on flow ([#774](https://github.com/bpmn-io/bpmn-js/issues/774))
* `FIX`: fix rules not preventing redundant loop ([#836](https://github.com/bpmn-io/bpmn-js/issues/836))
## 2.4.0
* `FEAT`: improve layouting of boundary event to host loops ([#467](https://github.com/bpmn-io/bpmn-js/issues/467))
* `FEAT`: allow circular activity to activity loops ([#824](https://github.com/bpmn-io/bpmn-js/issues/824))
* `FEAT`: create label on appropriate free position ([#825](https://github.com/bpmn-io/bpmn-js/issues/825))
* `CHORE`: bump to `diagram-js@2.5.0`
* `FIX`: repair label position not being adapted on host move
## 2.3.1
* `FIX`: revert to `Arial` as the default rendering font ([#819](https://github.com/bpmn-io/bpmn-js/issues/819))
* `FIX`: keep event definitions when switching from interrupting to non-interrupting boundary event ([#799](https://github.com/bpmn-io/bpmn-js/issues/799))
## 2.3.0
* `CHORE`: update to `diagram-js@2.4.0`
## 2.2.1
* `FIX`: correct updating of multiple data stores ([`300e7010`](https://github.com/bpmn-io/bpmn-js/commit/300e7010c4e1862394d147988dc4c4bcc09b07bc))
## 2.2.0
* `FEAT`: emit export events ([#813](https://github.com/bpmn-io/bpmn-js/issues/813))
* `FEAT`: unset businessObject name if empty ([`6c081d85`](https://github.com/bpmn-io/bpmn-js/commit/6c081d854fa8a4e87eb7cdd1744be37c78652667))
* `FEAT`: resize text annotation on text change ([`100f3fb2`](https://github.com/bpmn-io/bpmn-js/commit/100f3fb2ee6373cd4b7ad0b76e520a1afb70887e))
* `FIX`: apply data store behavior in collaboration only ([`5cc28d5d`](https://github.com/bpmn-io/bpmn-js/commit/5cc28d5d5571287a798b189aed75095f1fd0189e))
* `FIX`: create/update labels when updating element name via `Modeling#updateProperties` ([`4a0f6da8`](https://github.com/bpmn-io/bpmn-js/commit/4a0f6da814c45268e8a324e73a53479bd2435bbe))
## 2.1.0
* `FEAT`: support specifying `lineHeight` for text rendering ([#256](https://github.com/bpmn-io/diagram-js/pull/256))
* `FEAT`: `bpmn:LaneSet` elements get an ID assigned on creation
* `FEAT`: external labels can be deleted, clearing the elements name ([#791](https://github.com/bpmn-io/bpmn-js/pull/791))
* `FEAT`: add ability to override default element colors ([#713](https://github.com/bpmn-io/bpmn-js/issues/713))
* `FEAT`: add ability to override font family and size of rendered labels ([`4bb270f1`](https://github.com/bpmn-io/bpmn-js/commit/4bb270f19279db40f9cc3c179e09ee3a9a114e7c))
## 2.0.1
_Republish of `v2.0.0` due to registry error._
## 2.0.0
* `FEAT`: allow data store to be modeled between participants ([#483](https://github.com/bpmn-io/bpmn-js/issues/483))
* `CHORE`: update to [`diagram-js@2.0.0`](https://github.com/bpmn-io/diagram-js/blob/main/CHANGELOG.md#200)
* `FIX`: correctly handle missing `bpmndi:Label` bounds during model updating ([#794](https://github.com/bpmn-io/bpmn-js/issues/794))
### Breaking Changes
* The `PopupMenu` API got rewritten, cf. [`b1852e1d`](https://github.com/bpmn-io/diagram-js/pull/254/commits/b1852e1d71f67bd36ae1eb02748d2d0cbf124625)
## 1.3.3
* `CHORE`: update to [`bpmn-moddle@5.1.5`](https://github.com/bpmn-io/bpmn-moddle/blob/main/CHANGELOG.md#515)
## 1.3.2
* `FIX`: correctly serialize extension attributes on `bpmn:Expression`
## 1.3.1
* `FIX`: correctly auto-place from boundary events attached to host edges ([#788](https://github.com/bpmn-io/bpmn-js/issues/788))
## 1.3.0
* `FEAT`: expose additional `BpmnTreeWalker` APIs for advanced import use-cases
* `CHORE`: bump diagram-js and object-refs version
## 1.2.1
* `FIX`: correct side-effects config to not include `*.css` files
## 1.2.0
* `FEAT`: add initial snapping when creating associations
* `CHORE`: update to `diagram-js@1.3.0`
* `FIX`: allow message flows between collapsed pools
* `FIX`: complete direct editing on popup menu use
* `FIX`: focus label editing box on element creation
## 1.1.1
* `FIX`: escape `data-element-id` in CSS selectors
## 1.1.0
* `FEAT`: show gateway icon on context pad without marker ([`15dfab6b`](https://github.com/bpmn-io/bpmn-js/commit/15dfab6b5b12dd184acf070f2ab3ad205d1b245c))
## 1.0.4
* `FIX`: properly wire `$parent` on copy + paste
* `FIX`: improve boundary event rendering to correct SVG to image conversion
## 1.0.3
* `FIX`: re-expose `TestHelper#bootstrapBpmnJS` util
## 1.0.2
* `FIX`: correct library default export
## 1.0.1
_Republished 1.0.0 with CHANGELOG entries._
## 1.0.0
* `CHORE`: convert code base to ES modules
* `CHORE`: update utility toolbelt
### Breaking Changes
* You must now configure a module transpiler such as Babel or Webpack to handle ES module imports and exports.
## 0.31.0
* `FEAT`: encode entities in body properties during XML export
* `CHORE`: bump to [`bpmn-moddle@4.0.0`](https://github.com/bpmn-io/bpmn-moddle/releases/tag/v4.0.0)
* `CHORE`: bump utility version
## 0.30.0
* `CHORE`: bump to [`diagram-js@0.31.0`](https://github.com/bpmn-io/diagram-js/releases/tag/v0.31.0)
## ...
Check `git log` for earlier history.
================================================
FILE: LICENSE
================================================
Copyright (c) 2014-present Camunda Services GmbH
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in the
Software without restriction, including without limitation the rights to use, copy,
modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so, subject to the
following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
The source code responsible for displaying the bpmn.io project watermark that
links back to https://bpmn.io as part of rendered diagrams MUST NOT be
removed or changed. When this software is being used in a website or application,
the watermark must stay fully visible and not visually overlapped by other elements.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
OR OTHER DEALINGS IN THE SOFTWARE.
================================================
FILE: README.md
================================================
# bpmn-js - BPMN 2.0 for the web
[](https://github.com/bpmn-io/bpmn-js/actions?query=workflow%3ACI)
View and edit BPMN 2.0 diagrams in the browser.
[](http://demo.bpmn.io/s/start)
## Installation
Use the library [pre-packaged](https://github.com/bpmn-io/bpmn-js-examples/tree/main/pre-packaged)
or include it [via npm](https://github.com/bpmn-io/bpmn-js-examples/tree/main/bundling)
into your node-style web-application.
## Usage
To get started, create a [bpmn-js](https://github.com/bpmn-io/bpmn-js) instance
and render [BPMN 2.0 diagrams](https://www.omg.org/spec/BPMN/2.0.2/) in the browser:
```javascript
const xml = '...'; // my BPMN 2.0 xml
const viewer = new BpmnJS({
container: 'body'
});
try {
const { warnings } = await viewer.importXML(xml);
console.log('rendered');
} catch (err) {
console.log('error rendering', err);
}
```
Checkout our [examples](https://github.com/bpmn-io/bpmn-js-examples) for many
more supported usage scenarios.
## Resources
* [Demo](http://demo.bpmn.io)
* [Issues](https://github.com/bpmn-io/bpmn-js/issues)
* [Examples](https://github.com/bpmn-io/bpmn-js-examples)
* [Forum](https://forum.bpmn.io)
* [Changelog](./CHANGELOG.md)
## Build and Run
Prepare the project by installing all dependencies:
```sh
npm install
```
Then, depending on your use-case you may run any of the following commands:
```sh
# build the library and run all tests
npm run all
# spin up a single local modeler instance
npm start
# run the full development setup
npm run dev
```
You may need to perform [additional project setup](./docs/project/SETUP.md) when
building the latest development snapshot.
## Related
bpmn-js builds on top of a few powerful tools:
* [bpmn-moddle](https://github.com/bpmn-io/bpmn-moddle): Read / write support for BPMN 2.0 XML in the browsers
* [diagram-js](https://github.com/bpmn-io/diagram-js): Diagram rendering and editing toolkit
It is an extensible toolkit, complemented by many [additional utilities](https://github.com/bpmn-io/awesome-bpmn-io).
## License
Use under the terms of the [bpmn.io license](http://bpmn.io/license).
================================================
FILE: assets/bpmn-js.css
================================================
.bjs-container {
--bjs-font-family: Arial, sans-serif;
--color-grey-225-10-15: hsl(225, 10%, 15%);
--color-grey-225-10-35: hsl(225, 10%, 35%);
--color-grey-225-10-55: hsl(225, 10%, 55%);
--color-grey-225-10-75: hsl(225, 10%, 75%);
--color-grey-225-10-80: hsl(225, 10%, 80%);
--color-grey-225-10-85: hsl(225, 10%, 85%);
--color-grey-225-10-90: hsl(225, 10%, 90%);
--color-grey-225-10-95: hsl(225, 10%, 95%);
--color-grey-225-10-97: hsl(225, 10%, 97%);
--color-blue-205-100-45: hsl(205, 100%, 45%);
--color-blue-205-100-45-opacity-30: hsla(205, 100%, 45%, 30%);
--color-blue-205-100-50: hsl(205, 100%, 50%);
--color-blue-205-100-95: hsl(205, 100%, 95%);
--color-green-150-86-44: hsl(150, 86%, 44%);
--color-red-360-100-40: hsl(360, 100%, 40%);
--color-red-360-100-45: hsl(360, 100%, 45%);
--color-red-360-100-92: hsl(360, 100%, 92%);
--color-red-360-100-97: hsl(360, 100%, 97%);
--color-white: hsl(0, 0%, 100%);
--color-black: hsl(0, 0%, 0%);
--color-black-opacity-05: hsla(0, 0%, 0%, 5%);
--color-black-opacity-10: hsla(0, 0%, 0%, 10%);
--breadcrumbs-font-family: var(--bjs-font-family);
--breadcrumbs-item-color: var(--color-blue-205-100-50);
--breadcrumbs-arrow-color: var(--color-black);
--drilldown-fill-color: var(--color-white);
--drilldown-background-color: var(--color-blue-205-100-50);
}
.bjs-breadcrumbs {
position: absolute;
display: none;
flex-wrap: wrap;
align-items: center;
top: 30px;
left: 30px;
padding: 0px;
margin: 0px;
font-family: var(--breadcrumbs-font-family);
font-size: 16px;
line-height: normal;
}
.bjs-breadcrumbs-shown .bjs-breadcrumbs {
display: flex;
}
.djs-palette-shown .bjs-breadcrumbs {
left: 90px;
}
.djs-palette-shown.djs-palette-two-column .bjs-breadcrumbs {
left: 140px;
}
.bjs-breadcrumbs li {
display: inline-flex;
padding-bottom: 5px;
align-items: center;
}
.bjs-breadcrumbs li a {
cursor: pointer;
color: var(--breadcrumbs-item-color);
}
.bjs-breadcrumbs li:last-of-type a {
color: inherit;
cursor: default;
}
.bjs-breadcrumbs li:not(:first-child)::before {
content: url('data:image/svg+xml;utf8, ');
padding: 0 8px;
color: var(--breadcrumbs-arrow-color);
height: 1em;
}
.bjs-breadcrumbs .bjs-crumb {
display: inline-block;
max-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.bjs-drilldown {
width: 20px;
height: 20px;
padding: 0px;
margin-left: -20px;
cursor: pointer;
border: none;
border-radius: 2px;
outline: none;
fill: var(--drilldown-fill-color);
background-color: var(--drilldown-background-color);
}
.bjs-drilldown-empty {
display: none;
}
.selected .bjs-drilldown-empty {
display: inherit;
}
[data-popup="align-elements"] .djs-popup-results {
display: flex;
}
[data-popup="align-elements"] .djs-popup-body [data-group] + [data-group] {
border-left: 1px solid var(--popup-border-color);
}
[data-popup="align-elements"] [data-group="align"] {
display: grid;
grid-template-columns: repeat(3, 1fr);
}
[data-popup="align-elements"] .djs-popup-body .entry {
padding: 6px 8px;
}
[data-popup="align-elements"] .djs-popup-body .entry:not(:first-child) {
margin-top: 0;
}
[data-popup="align-elements"] .djs-popup-entry-icon {
display: block;
margin: 0;
height: 20px;
width: 20px;
}
================================================
FILE: docs/project/SETUP.md
================================================
# Project Setup
This document describes the necessary steps to setup a `bpmn-js` development environment.
## TLDR;
On Linux, OS X or Windows? [git](http://git-scm.com), [NodeJS](https://nodejs.org) and [npm](https://www.npmjs.org/doc/cli/npm.html) ready? Check out the [setup script section](#setup-via-script) below.
## Manual Steps
Make sure you have [git](http://git-scm.com), [NodeJS](https://nodejs.org) and [npm](https://www.npmjs.org/doc/cli/npm.html) installed before you continue.
### Get Project + Dependencies
The following projects from the [bpmn-io](https://github.com/bpmn-io) project on GitHub
* [bpmn-js](https://github.com/bpmn-io/bpmn-js)
* [diagram-js](https://github.com/bpmn-io/diagram-js)
* [bpmn-moddle](https://github.com/bpmn-io/bpmn-moddle)
and clone them into a common directory via
```sh
git clone git@github.com:bpmn-io/bpmn-js.git
git clone git@github.com:bpmn-io/diagram-js.git
git clone git@github.com:bpmn-io/bpmn-moddle.git
```
### Link Projects
[Link dependent projects](https://docs.npmjs.com/cli/link) between each other to pick up changes immediately.
```plain
.
├─bpmn-js
│ └─node_modules
│ ├─diagram-js
│ └─bpmn-moddle
├─diagram-js
├─bpmn-moddle
```
#### On OS X, Linux
Use [npm-link](https://docs.npmjs.com/cli/link) or `ln -s `.
#### On Windows
Use `mklink /d ` [(docs)](http://technet.microsoft.com/en-us/library/cc753194.aspx).
### Install Dependencies
Execute `npm install` on each of the projects to grab their dependencies.
### Verify Things are O.K.
Execute `npm run all` on each project. Things should be fine.
### Setup via Script
The whole setup can be automated through setup scripts for [Linux/OS X](./setup.sh) and [Windows](./setup.bat).
================================================
FILE: docs/project/setup-alternative.sh
================================================
#!/bin/bash
###
# Setup script to be executed in a bpmn.io project root (some empty folder chosen by YOU). Use if you do not want to rely on npm link.
###
base=`pwd`
echo cloning repositories
git clone git@github.com:bpmn-io/diagram-js.git
git clone git@github.com:bpmn-io/bpmn-js.git
git clone git@github.com:bpmn-io/bpmn-moddle.git
echo done.
echo setup diagram-js
cd $base/diagram-js
npm install
echo setup bpmn-moddle
cd $base/bpmn-moddle
npm install
echo setup bpmn-js
cd $base/bpmn-js
mkdir node_modules
ln -s $base/bpmn-moddle node_modules/bpmn-moddle
ln -s $base/diagram-js node_modules/diagram-js
npm install
cd $base
echo all done.
================================================
FILE: docs/project/setup.bat
================================================
@echo off
rem ###
rem # Setup script to be executed in a bpmn.io project root (some empty folder chosen by YOU)
rem ##
set BASE=%CD%
echo cloning repositories
git clone git@github.com:bpmn-io/diagram-js.git
git clone git@github.com:bpmn-io/bpmn-js.git
git clone git@github.com:bpmn-io/bpmn-moddle.git
echo done.
echo setup diagram-js
cd %BASE%\diagram-js
npm install
echo setup bpmn-moddle
cd %BASE%\bpmn-moddle
npm install
echo prepare setup bpmn-js
mkdir %BASE%\bpmn-js\node_modules
rem link bpmn-js
mklink /D %BASE%\bpmn-js\node_modules\bpmn-moddle %BASE%\bpmn-moddle
mklink /D %BASE%\bpmn-js\node_modules\diagram-js %BASE%\diagram-js
echo setup bpmn-js
cd %BASE%\bpmn-js
npm install
cd %BASE%
================================================
FILE: docs/project/setup.sh
================================================
#!/bin/bash
###
# Setup script to be executed in a bpmn.io project root (some empty folder chosen by YOU)
###
base=`pwd`
echo cloning repositories
git clone git@github.com:bpmn-io/diagram-js.git
git clone git@github.com:bpmn-io/bpmn-js.git
git clone git@github.com:bpmn-io/bpmn-moddle.git
echo done.
echo setup diagram-js
cd $base/diagram-js
npm install
echo setup bpmn-moddle
cd $base/bpmn-moddle
npm install
echo setup bpmn-js
cd $base/bpmn-js
npm install
npm link ../diagram-js
npm link ../bpmn-moddle
cd $base
echo all done.
================================================
FILE: docs/translations.json
================================================
[
"Activate create/remove space tool",
"Activate global connect tool",
"Activate hand tool",
"Activate lasso tool",
"Ad-hoc sub-process",
"Ad-hoc sub-process (collapsed)",
"Ad-hoc sub-process (expanded)",
"Add lane above",
"Add lane below",
"Add text annotation",
"Align elements",
"Align elements bottom",
"Align elements center",
"Align elements left",
"Align elements middle",
"Align elements right",
"Align elements top",
"Append compensation activity",
"Append conditional intermediate catch event",
"Append end event",
"Append gateway",
"Append intermediate/boundary event",
"Append message intermediate catch event",
"Append receive task",
"Append signal intermediate catch event",
"Append task",
"Append timer intermediate catch event",
"Business rule task",
"Call activity",
"Cancel boundary event",
"Cancel end event",
"Change element",
"Collection",
"Compensation boundary event",
"Compensation end event",
"Compensation intermediate throw event",
"Compensation start event",
"Complex gateway",
"Conditional boundary event",
"Conditional boundary event (non-interrupting)",
"Conditional flow",
"Conditional intermediate catch event",
"Conditional start event",
"Conditional start event (non-interrupting)",
"Connect to other element",
"Connect using association",
"Connect using data input association",
"Create data object reference",
"Create data store reference",
"Create end event",
"Create expanded sub-process",
"Create gateway",
"Create group",
"Create intermediate/boundary event",
"Create pool/participant",
"Create start event",
"Create task",
"Data object must be placed within a pool/participant.",
"Data object reference",
"Data store reference",
"Default flow",
"Delete",
"Distribute elements horizontally",
"Distribute elements vertically",
"Divide into three lanes",
"Divide into two lanes",
"Empty pool/participant",
"Empty pool/participant (removes content)",
"End event",
"Error boundary event",
"Error end event",
"Error start event",
"Escalation boundary event",
"Escalation boundary event (non-interrupting)",
"Escalation end event",
"Escalation intermediate throw event",
"Escalation start event",
"Escalation start event (non-interrupting)",
"Event sub-process",
"Event-based gateway",
"Exclusive gateway",
"Inclusive gateway",
"Intermediate throw event",
"Link intermediate catch event",
"Link intermediate throw event",
"Loop",
"Manual task",
"Message boundary event",
"Message boundary event (non-interrupting)",
"Message end event",
"Message intermediate catch event",
"Message intermediate throw event",
"Message start event",
"Message start event (non-interrupting)",
"Open {element}",
"Parallel gateway",
"Parallel multi-instance",
"Participant multiplicity",
"Receive task",
"Script task",
"Search in diagram",
"Send task",
"Sequence flow",
"Sequential multi-instance",
"Service task",
"Signal boundary event",
"Signal boundary event (non-interrupting)",
"Signal end event",
"Signal intermediate catch event",
"Signal intermediate throw event",
"Signal start event",
"Signal start event (non-interrupting)",
"Start event",
"Sub-process",
"Sub-process (collapsed)",
"Sub-process (expanded)",
"Task",
"Terminate end event",
"Timer boundary event",
"Timer boundary event (non-interrupting)",
"Timer intermediate catch event",
"Timer start event",
"Timer start event (non-interrupting)",
"Toggle non-interrupting",
"Transaction",
"User task",
"flow elements must be children of pools/participants"
]
================================================
FILE: eslint.config.mjs
================================================
import bpmnIoPlugin from 'eslint-plugin-bpmn-io';
const files = {
ignored: [
'dist',
'coverage'
],
build: [
'test/config/*.js',
'tasks/**/*.mjs',
'*.js',
'*.mjs'
],
test: [
'test/**/*.js'
]
};
export default [
{
ignores: files.ignored
},
// build
...bpmnIoPlugin.configs.node.map(config => {
return {
...config,
files: files.build
};
}),
// lib + test
...bpmnIoPlugin.configs.browser.map(config => {
return {
...config,
ignores: files.build
};
}),
// test
...bpmnIoPlugin.configs.mocha.map(config => {
return {
...config,
files: files.test,
};
}),
{
languageOptions: {
globals: {
sinon: true,
require: true
}
},
files: files.test
}
];
================================================
FILE: lib/BaseModeler.js
================================================
import inherits from 'inherits-browser';
import { Ids } from 'ids';
import BaseViewer from './BaseViewer';
/**
* @typedef {import('./BaseViewer').BaseViewerOptions} BaseViewerOptions
* @typedef {import('./BaseViewer').ModdleElementsById} ModdleElementsById
*
* @typedef {import('./model/Types').ModdleElement} ModdleElement
*/
/**
* A base modeler for BPMN 2.0 diagrams.
*
* See {@link bpmn-js/lib/Modeler} for a fully-featured modeler.
*
* @template [ServiceMap=null]
*
* @extends BaseViewer
*
* @param {BaseViewerOptions} [options] The options to configure the modeler.
*/
export default function BaseModeler(options) {
BaseViewer.call(this, options);
// hook ID collection into the modeler
this.on('import.parse.complete', function(event) {
if (!event.error) {
this._collectIds(event.definitions, event.elementsById);
}
}, this);
this.on('diagram.destroy', function() {
this.get('moddle').ids.clear();
}, this);
}
inherits(BaseModeler, BaseViewer);
/**
* Create a moddle instance, attaching IDs to it.
*
* @param {BaseViewerOptions} options
*
* @return {Moddle}
*/
BaseModeler.prototype._createModdle = function(options) {
var moddle = BaseViewer.prototype._createModdle.call(this, options);
// attach ids to moddle to be able to track and validated ids in the BPMN 2.0
// XML document tree
moddle.ids = new Ids([ 32, 36, 1 ]);
return moddle;
};
/**
* Collect IDs processed during parsing of the definitions object.
*
* @param {ModdleElement} definitions
* @param {ModdleElementsById} elementsById
*/
BaseModeler.prototype._collectIds = function(definitions, elementsById) {
var moddle = definitions.$model,
ids = moddle.ids,
id;
// remove references from previous import
ids.clear();
for (id in elementsById) {
ids.claim(id, elementsById[ id ]);
}
};
================================================
FILE: lib/BaseModeler.spec.ts
================================================
import Canvas from 'diagram-js/lib/core/Canvas';
import EventBus from 'diagram-js/lib/core/EventBus';
import BaseModeler from './BaseModeler';
import { testViewer } from './BaseViewer.spec';
const modeler = new BaseModeler({
container: 'container'
});
testViewer(modeler);
const otherModeler = new BaseModeler({
container: 'container'
});
const extendedModeler = new BaseModeler({
container: 'container',
alignToOrigin: false,
propertiesPanel: {
attachTo: '#properties-panel'
}
});
// typed API usage
type FooEvent = {
/**
* Very cool field!
*/
foo: string;
};
type EventMap = {
foo: FooEvent
};
type TypeMap = {
canvas: Canvas,
eventBus: EventBus
};
const typedModeler = new BaseModeler();
const bus = typedModeler.get('eventBus');
const canvas = typedModeler.get('canvas');
canvas.zoom('fit-viewport');
typedModeler.on('foo', event => {
console.log(event.foo);
});
typedModeler.get('eventBus').on('foo', e => console.log(e.foo));
================================================
FILE: lib/BaseViewer.js
================================================
/**
* The code in the area
* must not be changed.
*
* @see http://bpmn.io/license for more information.
*/
import {
assign,
find,
isNumber,
omit
} from 'min-dash';
import {
domify,
assignStyle,
query as domQuery,
remove as domRemove
} from 'min-dom';
import {
innerSVG
} from 'tiny-svg';
import Diagram from 'diagram-js';
import { BpmnModdle } from 'bpmn-moddle';
import inherits from 'inherits-browser';
import {
importBpmnDiagram
} from './import/Importer';
/**
* @template T
*
* @typedef { import('diagram-js/lib/core/EventBus').default } EventBus
*/
/**
* @template T
*
* @typedef {import('diagram-js/lib/core/EventBus').EventBusEventCallback} EventBusEventCallback
*/
/**
* @typedef {import('didi').ModuleDeclaration} ModuleDeclaration
*
* @typedef {import('./model/Types').Moddle} Moddle
* @typedef {import('./model/Types').ModdleElement} ModdleElement
* @typedef {import('./model/Types').ModdleExtension} ModdleExtension
*
* @typedef { {
* width?: number|string;
* height?: number|string;
* position?: string;
* container?: string|HTMLElement;
* moddleExtensions?: ModdleExtensions;
* additionalModules?: ModuleDeclaration[];
* } & Record } BaseViewerOptions
*
* @typedef {Record} ModdleElementsById
*
* @typedef { {
* [key: string]: ModdleExtension;
* } } ModdleExtensions
*
* @typedef { {
* warnings: string[];
* } } ImportXMLResult
*
* @typedef {ImportXMLResult & Error} ImportXMLError
*
* @typedef {ImportXMLResult} ImportDefinitionsResult
*
* @typedef {ImportXMLError} ImportDefinitionsError
*
* @typedef {ImportXMLResult} OpenResult
*
* @typedef {ImportXMLError} OpenError
*
* @typedef { {
* format?: boolean;
* preamble?: boolean;
* } } SaveXMLOptions
*
* @typedef { {
* xml?: string;
* error?: Error;
* } } SaveXMLResult
*
* @typedef { {
* svg: string;
* } } SaveSVGResult
*
* @typedef { {
* xml: string;
* } } ImportParseStartEvent
*
* @typedef { {
* error?: ImportXMLError;
* definitions?: ModdleElement;
* elementsById?: ModdleElementsById;
* references?: ModdleElement[];
* warnings: string[];
* } } ImportParseCompleteEvent
*
* @typedef { {
* error?: ImportXMLError;
* warnings: string[];
* } } ImportDoneEvent
*
* @typedef { {
* definitions: ModdleElement;
* } } SaveXMLStartEvent
*
* @typedef {SaveXMLResult} SaveXMLDoneEvent
*
* @typedef { {
* error?: Error;
* svg: string;
* } } SaveSVGDoneEvent
*/
/**
* @template Type
*
* @typedef { Type extends { eventBus: EventBus } ? X : never } EventMap
*/
/**
* A base viewer for BPMN 2.0 diagrams.
*
* Have a look at {@link bpmn-js/lib/Viewer}, {@link bpmn-js/lib/NavigatedViewer} or {@link bpmn-js/lib/Modeler} for
* bundles that include actual features.
*
* @template [ServiceMap=null]
*
* @extends Diagram
*
* @param {BaseViewerOptions} [options] The options to configure the viewer.
*/
export default function BaseViewer(options) {
/**
* @type {BaseViewerOptions}
*/
options = assign({}, DEFAULT_OPTIONS, options);
/**
* @type {Moddle}
*/
this._moddle = this._createModdle(options);
/**
* @type {HTMLElement}
*/
this._container = this._createContainer(options);
this._init(this._container, this._moddle, options);
/* */
addProjectLogo(this._container);
/* */
}
inherits(BaseViewer, Diagram);
/**
* Parse and render a BPMN 2.0 diagram.
*
* Once finished the viewer reports back the result to the
* provided callback function with (err, warnings).
*
* ## Life-Cycle Events
*
* During import the viewer will fire life-cycle events:
*
* * import.parse.start (about to read model from XML)
* * import.parse.complete (model read; may have worked or not)
* * import.render.start (graphical import start)
* * import.render.complete (graphical import finished)
* * import.done (everything done)
*
* You can use these events to hook into the life-cycle.
*
* @throws {ImportXMLError} An error thrown during the import of the XML.
*
* @fires BaseViewer#ImportParseStartEvent
* @fires BaseViewer#ImportParseCompleteEvent
* @fires Importer#ImportRenderStartEvent
* @fires Importer#ImportRenderCompleteEvent
* @fires BaseViewer#ImportDoneEvent
*
* @param {string} xml The BPMN 2.0 XML to be imported.
* @param {ModdleElement|string} [bpmnDiagram] The optional diagram or Id of the BPMN diagram to open.
*
* @return {Promise} A promise resolving with warnings that were produced during the import.
*/
BaseViewer.prototype.importXML = async function importXML(xml, bpmnDiagram) {
const self = this;
function ParseCompleteEvent(data) {
return self.get('eventBus').createEvent(data);
}
let aggregatedWarnings = [];
try {
// hook in pre-parse listeners +
// allow xml manipulation
/**
* A `import.parse.start` event.
*
* @event BaseViewer#ImportParseStartEvent
* @type {ImportParseStartEvent}
*/
xml = this._emit('import.parse.start', { xml: xml }) || xml;
let parseResult;
try {
parseResult = await this._moddle.fromXML(xml, 'bpmn:Definitions');
} catch (error) {
this._emit('import.parse.complete', {
error
});
throw error;
}
let definitions = parseResult.rootElement;
const references = parseResult.references;
const parseWarnings = parseResult.warnings;
const elementsById = parseResult.elementsById;
aggregatedWarnings = aggregatedWarnings.concat(parseWarnings);
// hook in post parse listeners +
// allow definitions manipulation
/**
* A `import.parse.complete` event.
*
* @event BaseViewer#ImportParseCompleteEvent
* @type {ImportParseCompleteEvent}
*/
definitions = this._emit('import.parse.complete', ParseCompleteEvent({
error: null,
definitions: definitions,
elementsById: elementsById,
references: references,
warnings: aggregatedWarnings
})) || definitions;
const importResult = await this.importDefinitions(definitions, bpmnDiagram);
aggregatedWarnings = aggregatedWarnings.concat(importResult.warnings);
/**
* A `import.parse.complete` event.
*
* @event BaseViewer#ImportDoneEvent
* @type {ImportDoneEvent}
*/
this._emit('import.done', { error: null, warnings: aggregatedWarnings });
return { warnings: aggregatedWarnings };
} catch (err) {
let error = err;
aggregatedWarnings = aggregatedWarnings.concat(error.warnings || []);
addWarningsToError(error, aggregatedWarnings);
error = checkValidationError(error);
this._emit('import.done', { error, warnings: error.warnings });
throw error;
}
};
/**
* Import parsed definitions and render a BPMN 2.0 diagram.
*
* Once finished the viewer reports back the result to the
* provided callback function with (err, warnings).
*
* ## Life-Cycle Events
*
* During import the viewer will fire life-cycle events:
*
* * import.render.start (graphical import start)
* * import.render.complete (graphical import finished)
*
* You can use these events to hook into the life-cycle.
*
* @throws {ImportDefinitionsError} An error thrown during the import of the definitions.
*
* @param {ModdleElement} definitions The definitions.
* @param {ModdleElement|string} [bpmnDiagram] The optional diagram or ID of the BPMN diagram to open.
*
* @return {Promise} A promise resolving with warnings that were produced during the import.
*/
BaseViewer.prototype.importDefinitions = async function importDefinitions(definitions, bpmnDiagram) {
this._setDefinitions(definitions);
const result = await this.open(bpmnDiagram);
return { warnings: result.warnings };
};
/**
* Open diagram of previously imported XML.
*
* Once finished the viewer reports back the result to the
* provided callback function with (err, warnings).
*
* ## Life-Cycle Events
*
* During switch the viewer will fire life-cycle events:
*
* * import.render.start (graphical import start)
* * import.render.complete (graphical import finished)
*
* You can use these events to hook into the life-cycle.
*
* @throws {OpenError} An error thrown during opening.
*
* @param {ModdleElement|string} bpmnDiagramOrId The diagram or Id of the BPMN diagram to open.
*
* @return {Promise} A promise resolving with warnings that were produced during opening.
*/
BaseViewer.prototype.open = async function open(bpmnDiagramOrId) {
const definitions = this._definitions;
let bpmnDiagram = bpmnDiagramOrId;
if (!definitions) {
const error = new Error('no XML imported');
addWarningsToError(error, []);
throw error;
}
if (typeof bpmnDiagramOrId === 'string') {
bpmnDiagram = findBPMNDiagram(definitions, bpmnDiagramOrId);
if (!bpmnDiagram) {
const error = new Error('BPMNDiagram <' + bpmnDiagramOrId + '> not found');
addWarningsToError(error, []);
throw error;
}
}
// clear existing rendered diagram
// catch synchronous exceptions during #clear()
try {
this.clear();
} catch (error) {
addWarningsToError(error, []);
throw error;
}
// perform graphical import
const { warnings } = await importBpmnDiagram(this, definitions, bpmnDiagram);
return { warnings };
};
/**
* Export the currently displayed BPMN 2.0 diagram as
* a BPMN 2.0 XML document.
*
* ## Life-Cycle Events
*
* During XML saving the viewer will fire life-cycle events:
*
* * saveXML.start (before serialization)
* * saveXML.serialized (after xml generation)
* * saveXML.done (everything done)
*
* You can use these events to hook into the life-cycle.
*
* @throws {Error} An error thrown during export.
*
* @fires BaseViewer#SaveXMLStart
* @fires BaseViewer#SaveXMLDone
*
* @param {SaveXMLOptions} [options] The options.
*
* @return {Promise} A promise resolving with the XML.
*/
BaseViewer.prototype.saveXML = async function saveXML(options) {
options = options || {};
let definitions = this._definitions,
error, xml;
try {
if (!definitions) {
throw new Error('no definitions loaded');
}
// allow to fiddle around with definitions
/**
* A `saveXML.start` event.
*
* @event BaseViewer#SaveXMLStartEvent
* @type {SaveXMLStartEvent}
*/
definitions = this._emit('saveXML.start', {
definitions
}) || definitions;
const result = await this._moddle.toXML(definitions, options);
xml = result.xml;
xml = this._emit('saveXML.serialized', {
xml
}) || xml;
} catch (err) {
error = err;
}
const result = error ? { error } : { xml };
/**
* A `saveXML.done` event.
*
* @event BaseViewer#SaveXMLDoneEvent
* @type {SaveXMLDoneEvent}
*/
this._emit('saveXML.done', result);
if (error) {
throw error;
}
return result;
};
/**
* Export the currently displayed BPMN 2.0 diagram as
* an SVG image.
*
* ## Life-Cycle Events
*
* During SVG saving the viewer will fire life-cycle events:
*
* * saveSVG.start (before serialization)
* * saveSVG.done (everything done)
*
* You can use these events to hook into the life-cycle.
*
* @throws {Error} An error thrown during export.
*
* @fires BaseViewer#SaveSVGDone
*
* @return {Promise} A promise resolving with the SVG.
*/
BaseViewer.prototype.saveSVG = async function saveSVG() {
this._emit('saveSVG.start');
let svg, err;
try {
const canvas = this.get('canvas');
const contentNode = canvas.getActiveLayer(),
defsNode = domQuery(':scope > defs', canvas._svg);
const contents = innerSVG(contentNode),
defs = defsNode ? '' + innerSVG(defsNode) + ' ' : '';
const bbox = contentNode.getBBox();
svg =
'\n' +
'\n' +
'\n' +
'' +
defs + contents +
' ';
} catch (e) {
err = e;
}
/**
* A `saveSVG.done` event.
*
* @event BaseViewer#SaveSVGDoneEvent
* @type {SaveSVGDoneEvent}
*/
this._emit('saveSVG.done', {
error: err,
svg: svg
});
if (err) {
throw err;
}
return { svg };
};
BaseViewer.prototype._setDefinitions = function(definitions) {
this._definitions = definitions;
};
/**
* Return modules to instantiate with.
*
* @return {ModuleDeclaration[]} The modules.
*/
BaseViewer.prototype.getModules = function() {
return this._modules;
};
/**
* Remove all drawn elements from the viewer.
*
* After calling this method the viewer can still be reused for opening another
* diagram.
*/
BaseViewer.prototype.clear = function() {
if (!this.getDefinitions()) {
// no diagram to clear
return;
}
// remove drawn elements
Diagram.prototype.clear.call(this);
};
/**
* Destroy the viewer instance and remove all its remainders from the document
* tree.
*/
BaseViewer.prototype.destroy = function() {
// diagram destroy
Diagram.prototype.destroy.call(this);
// dom detach
domRemove(this._container);
};
/**
* @overlord
*
* Register an event listener for events with the given name.
*
* The callback will be invoked with `event, ...additionalArguments`
* that have been passed to {@link EventBus#fire}.
*
* Returning false from a listener will prevent the events default action
* (if any is specified). To stop an event from being processed further in
* other listeners execute {@link Event#stopPropagation}.
*
* Returning anything but `undefined` from a listener will stop the listener propagation.
*
* @template T
*
* @param {string|string[]} events The event(s) to listen to.
* @param {number} [priority] The priority with which to listen.
* @param {EventBusEventCallback} callback The callback.
* @param {any} [that] Value of `this` the callback will be called with.
*/
/**
* Register an event listener for events with the given name.
*
* The callback will be invoked with `event, ...additionalArguments`
* that have been passed to {@link EventBus#fire}.
*
* Returning false from a listener will prevent the events default action
* (if any is specified). To stop an event from being processed further in
* other listeners execute {@link Event#stopPropagation}.
*
* Returning anything but `undefined` from a listener will stop the listener propagation.
*
* @template {keyof EventMap} EventName
*
* @param {EventName} events to subscribe to
* @param {number} [priority=1000] listen priority
* @param {EventBusEventCallback<(EventMap)[EventName]>} callback
* @param {any} [that] callback context
*/
BaseViewer.prototype.on = function(events, priority, callback, that) {
return this.get('eventBus').on(events, priority, callback, that);
};
/**
* Remove an event listener.
*
* @param {string|string[]} events The event(s).
* @param {Function} [callback] The callback.
*/
BaseViewer.prototype.off = function(events, callback) {
this.get('eventBus').off(events, callback);
};
/**
* Attach the viewer to an HTML element.
*
* @param {HTMLElement} parentNode The parent node to attach to.
*/
BaseViewer.prototype.attachTo = function(parentNode) {
if (!parentNode) {
throw new Error('parentNode required');
}
// ensure we detach from the
// previous, old parent
this.detach();
// unwrap jQuery if provided
if (parentNode.get && parentNode.constructor.prototype.jquery) {
parentNode = parentNode.get(0);
}
if (typeof parentNode === 'string') {
parentNode = domQuery(parentNode);
}
parentNode.appendChild(this._container);
this._emit('attach', {});
this.get('canvas').resized();
};
/**
* Get the definitions model element.
*
* @return {ModdleElement} The definitions model element.
*/
BaseViewer.prototype.getDefinitions = function() {
return this._definitions;
};
/**
* Detach the viewer.
*
* @fires BaseViewer#DetachEvent
*/
BaseViewer.prototype.detach = function() {
const container = this._container,
parentNode = container.parentNode;
if (!parentNode) {
return;
}
/**
* A `detach` event.
*
* @event BaseViewer#DetachEvent
* @type {Object}
*/
this._emit('detach', {});
parentNode.removeChild(container);
};
BaseViewer.prototype._init = function(container, moddle, options) {
const baseModules = options.modules || this.getModules(options),
additionalModules = options.additionalModules || [],
staticModules = [
{
bpmnjs: [ 'value', this ],
moddle: [ 'value', moddle ]
}
];
const diagramModules = [].concat(staticModules, baseModules, additionalModules);
const diagramOptions = assign(omit(options, [ 'additionalModules' ]), {
canvas: assign({}, options.canvas, { container: container }),
modules: diagramModules
});
// invoke diagram constructor
Diagram.call(this, diagramOptions);
if (options && options.container) {
this.attachTo(options.container);
}
};
/**
* Emit an event on the underlying {@link EventBus}
*
* @param {string} type
* @param {Object} event
*
* @return {Object} The return value after calling all event listeners.
*/
BaseViewer.prototype._emit = function(type, event) {
return this.get('eventBus').fire(type, event);
};
/**
* @param {BaseViewerOptions} options
*
* @return {HTMLElement}
*/
BaseViewer.prototype._createContainer = function(options) {
const container = domify('
');
assignStyle(container, {
width: ensureUnit(options.width),
height: ensureUnit(options.height),
position: options.position
});
return container;
};
/**
* @param {BaseViewerOptions} options
*
* @return {Moddle}
*/
BaseViewer.prototype._createModdle = function(options) {
const moddleOptions = assign({}, this._moddleExtensions, options.moddleExtensions);
return new BpmnModdle(moddleOptions);
};
BaseViewer.prototype._modules = [];
// helpers ///////////////
function addWarningsToError(err, warningsAry) {
err.warnings = warningsAry;
return err;
}
function checkValidationError(err) {
// check if we can help the user by indicating wrong BPMN 2.0 xml
// (in case he or the exporting tool did not get that right)
const pattern = /unparsable content <([^>]+)> detected([\s\S]*)$/;
const match = pattern.exec(err.message);
if (match) {
err.message =
'unparsable content <' + match[1] + '> detected; ' +
'this may indicate an invalid BPMN 2.0 diagram file' + match[2];
}
return err;
}
const DEFAULT_OPTIONS = {
width: '100%',
height: '100%',
position: 'relative'
};
/**
* Ensure the passed argument is a proper unit (defaulting to px)
*/
function ensureUnit(val) {
return val + (isNumber(val) ? 'px' : '');
}
/**
* Find BPMNDiagram in definitions by ID
*
* @param {ModdleElement} definitions
* @param {string} diagramId
*
* @return {ModdleElement|null}
*/
function findBPMNDiagram(definitions, diagramId) {
if (!diagramId) {
return null;
}
return find(definitions.diagrams, function(element) {
return element.id === diagramId;
}) || null;
}
/* */
import {
open as openPoweredBy,
BPMNIO_IMG,
LOGO_STYLES,
LINK_STYLES
} from './util/PoweredByUtil';
import {
event as domEvent
} from 'min-dom';
/**
* Adds the project logo to the diagram container as
* required by the bpmn.io license.
*
* @see http://bpmn.io/license
*
* @param {Element} container
*/
function addProjectLogo(container) {
const img = BPMNIO_IMG;
const linkMarkup =
'' +
img +
' ';
const linkElement = domify(linkMarkup);
assignStyle(domQuery('svg', linkElement), LOGO_STYLES);
assignStyle(linkElement, LINK_STYLES, {
position: 'absolute',
bottom: '15px',
right: '15px',
zIndex: '100'
});
container.appendChild(linkElement);
domEvent.bind(linkElement, 'click', function(event) {
openPoweredBy();
event.preventDefault();
});
}
/* */
================================================
FILE: lib/BaseViewer.spec.ts
================================================
import CommandStack from 'diagram-js/lib/command/CommandStack';
import EventBus, { Event } from 'diagram-js/lib/core/EventBus';
import BaseViewer, {
ImportDoneEvent,
ImportParseCompleteEvent,
ImportParseStartEvent,
SaveXMLDoneEvent,
SaveXMLStartEvent
} from './BaseViewer';
import OverlaysModule from 'diagram-js/lib/features/overlays';
import Canvas from 'diagram-js/lib/core/Canvas';
const viewer = new BaseViewer();
const configuredViewer = new BaseViewer({
width: 100,
height: 100,
position: 'absolute',
container: 'container',
moddleExtensions: {
foo: {}
},
additionalModules: [
OverlaysModule
]
});
testViewer(viewer);
const extendedViewer = new BaseViewer({
container: 'container',
alignToOrigin: false,
propertiesPanel: {
attachTo: '#properties-panel'
}
});
export function testViewer(viewer: BaseViewer) {
viewer.importXML('', 'BPMNDiagram_1');
viewer.importXML('')
.then(({ warnings }) => {
console.log(warnings);
})
.catch(error => {
const {
message,
warnings
} = error;
console.log(message, warnings);
});
viewer.importDefinitions({ $type: 'bpmn:Definitions' }, 'BPMNDiagram_1');
viewer.importDefinitions({ $type: 'bpmn:Definitions' })
.then(({ warnings }) => {
console.log(warnings);
})
.catch(error => {
const {
message,
warnings
} = error;
console.log(message, warnings);
});
viewer.open('BPMNDiagram_1');
viewer.open({ $type: 'bpmn:BPMNDiagram' })
.then(({ warnings }) => {
console.log(warnings);
})
.catch(error => {
const {
message,
warnings
} = error;
console.log(message, warnings);
});
viewer.saveXML({ format: true, preamble: false })
.then(({ xml, error }) => {
if (error) {
console.log(error);
} else {
console.log(xml);
}
})
.catch(error => {
console.log(error);
});
viewer.saveXML();
viewer.saveSVG();
viewer.getModules();
viewer.clear();
viewer.destroy();
viewer.get('commandStack').undo();
viewer.invoke((commandStack: CommandStack) => commandStack.undo());
viewer.on('foo', () => console.log('foo'));
viewer.on([ 'foo', 'bar' ], () => console.log('foo'));
viewer.on('foo', 2000, () => console.log('foo'));
viewer.on('foo', 2000, () => console.log('foo'), { foo: 'bar' });
viewer.off('foo', () => console.log('foo'));
viewer.attachTo(document.createElement('div'));
viewer.getDefinitions();
viewer.detach();
viewer.on('import.parse.start', ({ xml }) => {
console.log(xml);
});
viewer.on('import.parse.complete', ({
error,
definitions,
elementsById,
references,
warnings
}) => {
if (error) {
console.error(error);
}
if (warnings.length) {
warnings.forEach(warning => console.log(warning));
}
console.log(definitions, elementsById, references);
});
viewer.on('import.done', ({ error, warnings }) => {
if (error) {
console.error(error);
}
if (warnings.length) {
warnings.forEach(warning => console.log(warning));
}
});
viewer.on('saveXML.start', ({ definitions }) => {
console.log(definitions);
});
viewer.on('saveXML.done', ({ error, xml }) => {
if (error) {
console.error(error);
} else {
console.log(xml);
}
});
viewer.on('detach', () => {});
}
// typed API usage
type FooEvent = {
/**
* Very cool field!
*/
foo: string;
};
type EventMap = {
foo: FooEvent
};
type TypeMap = {
canvas: Canvas,
eventBus: EventBus
};
const typedViewer = new BaseViewer();
const bus = typedViewer.get('eventBus');
const canvas = typedViewer.get('canvas');
canvas.zoom('fit-viewport');
typedViewer.on('foo', event => {
console.log(event.foo);
});
typedViewer.get('eventBus').on('foo', e => console.log(e.foo));
================================================
FILE: lib/Modeler.js
================================================
import inherits from 'inherits-browser';
import BaseModeler from './BaseModeler';
import Viewer from './Viewer';
import NavigatedViewer from './NavigatedViewer';
import KeyboardMoveModule from 'diagram-js/lib/navigation/keyboard-move';
import MoveCanvasModule from 'diagram-js/lib/navigation/movecanvas';
import ZoomScrollModule from 'diagram-js/lib/navigation/zoomscroll';
import AlignElementsModule from './features/align-elements';
import AutoPlaceModule from './features/auto-place';
import AutoResizeModule from './features/auto-resize';
import AutoScrollModule from 'diagram-js/lib/features/auto-scroll';
import BendpointsModule from 'diagram-js/lib/features/bendpoints';
import ConnectModule from 'diagram-js/lib/features/connect';
import ConnectionPreviewModule from 'diagram-js/lib/features/connection-preview';
import ContextPadModule from './features/context-pad';
import CopyPasteModule from './features/copy-paste';
import CreateModule from 'diagram-js/lib/features/create';
import DistributeElementsModule from './features/distribute-elements';
import EditorActionsModule from './features/editor-actions';
import GridSnappingModule from './features/grid-snapping';
import InteractionEventsModule from './features/interaction-events';
import KeyboardModule from './features/keyboard';
import KeyboardMoveSelectionModule from 'diagram-js/lib/features/keyboard-move-selection';
import LabelEditingModule from './features/label-editing';
import LabelLink from './features/label-link';
import ModelingModule from './features/modeling';
import ModelingFeedbackModule from './features/modeling-feedback';
import MoveModule from 'diagram-js/lib/features/move';
import PaletteModule from './features/palette';
import ReplacePreviewModule from './features/replace-preview';
import ResizeModule from 'diagram-js/lib/features/resize';
import SnappingModule from './features/snapping';
import SearchModule from './features/search';
import OutlineModule from './features/outline';
var initialDiagram =
'' +
'' +
'' +
' ' +
' ' +
'' +
'' +
'' +
' ' +
' ' +
' ' +
' ' +
' ';
/**
* @typedef {import('./BaseViewer').BaseViewerOptions} BaseViewerOptions
* @typedef {import('./BaseViewer').ImportXMLResult} ImportXMLResult
*/
/**
* A modeler for BPMN 2.0 diagrams.
*
* ## Extending the Modeler
*
* In order to extend the viewer pass extension modules to bootstrap via the
* `additionalModules` option. An extension module is an object that exposes
* named services.
*
* The following example depicts the integration of a simple
* logging component that integrates with interaction events:
*
*
* ```javascript
*
* // logging component
* function InteractionLogger(eventBus) {
* eventBus.on('element.hover', function(event) {
* console.log()
* })
* }
*
* InteractionLogger.$inject = [ 'eventBus' ]; // minification save
*
* // extension module
* var extensionModule = {
* __init__: [ 'interactionLogger' ],
* interactionLogger: [ 'type', InteractionLogger ]
* };
*
* // extend the viewer
* var bpmnModeler = new Modeler({ additionalModules: [ extensionModule ] });
* bpmnModeler.importXML(...);
* ```
*
*
* ## Customizing / Replacing Components
*
* You can replace individual diagram components by redefining them in override modules.
* This works for all components, including those defined in the core.
*
* Pass in override modules via the `options.additionalModules` flag like this:
*
* ```javascript
* function CustomContextPadProvider(contextPad) {
*
* contextPad.registerProvider(this);
*
* this.getContextPadEntries = function(element) {
* // no entries, effectively disable the context pad
* return {};
* };
* }
*
* CustomContextPadProvider.$inject = [ 'contextPad' ];
*
* var overrideModule = {
* contextPadProvider: [ 'type', CustomContextPadProvider ]
* };
*
* var bpmnModeler = new Modeler({ additionalModules: [ overrideModule ]});
* ```
*
* @template [ServiceMap=null]
*
* @extends BaseModeler
*
* @param {BaseViewerOptions} [options] The options to configure the modeler.
*/
export default function Modeler(options) {
BaseModeler.call(this, options);
}
inherits(Modeler, BaseModeler);
Modeler.Viewer = Viewer;
Modeler.NavigatedViewer = NavigatedViewer;
/**
* Create a new diagram to start modeling.
*
* @throws {ImportXMLError} An error thrown during the import of the XML.
*
* @return {Promise} A promise resolving with warnings that were produced during the import.
*/
Modeler.prototype.createDiagram = function createDiagram() {
return this.importXML(initialDiagram);
};
Modeler.prototype._interactionModules = [
// non-modeling components
KeyboardMoveModule,
MoveCanvasModule,
ZoomScrollModule
];
Modeler.prototype._modelingModules = [
// modeling components
AlignElementsModule,
AutoPlaceModule,
AutoScrollModule,
AutoResizeModule,
BendpointsModule,
ConnectModule,
ConnectionPreviewModule,
ContextPadModule,
CopyPasteModule,
CreateModule,
DistributeElementsModule,
EditorActionsModule,
GridSnappingModule,
InteractionEventsModule,
KeyboardModule,
KeyboardMoveSelectionModule,
LabelEditingModule,
LabelLink,
ModelingModule,
ModelingFeedbackModule,
MoveModule,
PaletteModule,
ReplacePreviewModule,
ResizeModule,
SnappingModule,
SearchModule,
OutlineModule
];
// modules the modeler is composed of
//
// - viewer modules
// - interaction modules
// - modeling modules
Modeler.prototype._modules = [].concat(
Viewer.prototype._modules,
Modeler.prototype._interactionModules,
Modeler.prototype._modelingModules
);
================================================
FILE: lib/Modeler.spec.ts
================================================
import Canvas from 'diagram-js/lib/core/Canvas';
import EventBus from 'diagram-js/lib/core/EventBus';
import Modeler from './Modeler';
import { testViewer } from './BaseViewer.spec';
const modeler = new Modeler({
container: 'container'
});
testViewer(modeler);
modeler.createDiagram();
const otherModeler = new Modeler({
container: 'container'
});
const extendedModeler = new Modeler({
container: 'container',
alignToOrigin: false,
propertiesPanel: {
attachTo: '#properties-panel'
}
});
// typed API usage
type FooEvent = {
/**
* Very cool field!
*/
foo: string;
};
type EventMap = {
foo: FooEvent
};
type TypeMap = {
canvas: Canvas,
eventBus: EventBus
};
const typedViewer = new Modeler();
const bus = typedViewer.get('eventBus');
const canvas = typedViewer.get('canvas');
canvas.zoom('fit-viewport');
typedViewer.on('foo', event => {
console.log(event.foo);
});
typedViewer.get('eventBus').on('foo', e => console.log(e.foo));
================================================
FILE: lib/NavigatedViewer.js
================================================
import inherits from 'inherits-browser';
import Viewer from './Viewer';
import KeyboardMoveModule from 'diagram-js/lib/navigation/keyboard-move';
import MoveCanvasModule from 'diagram-js/lib/navigation/movecanvas';
import ZoomScrollModule from 'diagram-js/lib/navigation/zoomscroll';
/**
* @typedef { import('./BaseViewer').BaseViewerOptions } BaseViewerOptions
*/
/**
* A viewer with mouse and keyboard navigation features.
*
* @template [ServiceMap=null]
*
* @extends Viewer
*
* @param {BaseViewerOptions} [options]
*/
export default function NavigatedViewer(options) {
Viewer.call(this, options);
}
inherits(NavigatedViewer, Viewer);
NavigatedViewer.prototype._navigationModules = [
KeyboardMoveModule,
MoveCanvasModule,
ZoomScrollModule
];
NavigatedViewer.prototype._modules = [].concat(
Viewer.prototype._modules,
NavigatedViewer.prototype._navigationModules
);
================================================
FILE: lib/NavigatedViewer.spec.ts
================================================
import Canvas from 'diagram-js/lib/core/Canvas';
import EventBus from 'diagram-js/lib/core/EventBus';
import NavigatedViewer from './NavigatedViewer';
import { testViewer } from './BaseViewer.spec';
const viewer = new NavigatedViewer({
container: 'container'
});
testViewer(viewer);
const extendedViewer = new NavigatedViewer({
container: 'container',
alignToOrigin: false,
propertiesPanel: {
attachTo: '#properties-panel'
}
});
// typed API usage
type FooEvent = {
/**
* Very cool field!
*/
foo: string;
};
type EventMap = {
foo: FooEvent
};
type TypeMap = {
canvas: Canvas,
eventBus: EventBus
};
const typedViewer = new NavigatedViewer();
const bus = typedViewer.get('eventBus');
const canvas = typedViewer.get('canvas');
canvas.zoom('fit-viewport');
typedViewer.on('foo', event => {
console.log(event.foo);
});
typedViewer.get('eventBus').on('foo', e => console.log(e.foo));
================================================
FILE: lib/Viewer.js
================================================
import inherits from 'inherits-browser';
import CoreModule from './core';
import DrilldownModdule from './features/drilldown';
import OverlaysModule from 'diagram-js/lib/features/overlays';
import SelectionModule from 'diagram-js/lib/features/selection';
import TranslateModule from 'diagram-js/lib/i18n/translate';
import BaseViewer from './BaseViewer';
/**
* @typedef { import('./BaseViewer').BaseViewerOptions } BaseViewerOptions
*/
/**
* A viewer for BPMN 2.0 diagrams.
*
* Have a look at {@link bpmn-js/lib/NavigatedViewer} or {@link bpmn-js/lib/Modeler} for bundles that include
* additional features.
*
*
* ## Extending the Viewer
*
* In order to extend the viewer pass extension modules to bootstrap via the
* `additionalModules` option. An extension module is an object that exposes
* named services.
*
* The following example depicts the integration of a simple
* logging component that integrates with interaction events:
*
*
* ```javascript
*
* // logging component
* function InteractionLogger(eventBus) {
* eventBus.on('element.hover', function(event) {
* console.log()
* })
* }
*
* InteractionLogger.$inject = [ 'eventBus' ]; // minification save
*
* // extension module
* var extensionModule = {
* __init__: [ 'interactionLogger' ],
* interactionLogger: [ 'type', InteractionLogger ]
* };
*
* // extend the viewer
* var bpmnViewer = new Viewer({ additionalModules: [ extensionModule ] });
* bpmnViewer.importXML(...);
* ```
*
* @template [ServiceMap=null]
*
* @extends BaseViewer
*
* @param {BaseViewerOptions} [options] The options to configure the viewer.
*/
export default function Viewer(options) {
BaseViewer.call(this, options);
}
inherits(Viewer, BaseViewer);
// modules the viewer is composed of
Viewer.prototype._modules = [
CoreModule,
DrilldownModdule,
OverlaysModule,
SelectionModule,
TranslateModule
];
// default moddle extensions the viewer is composed of
Viewer.prototype._moddleExtensions = {};
================================================
FILE: lib/Viewer.spec.ts
================================================
import Canvas from 'diagram-js/lib/core/Canvas';
import EventBus from 'diagram-js/lib/core/EventBus';
import Viewer from './Viewer';
import { testViewer } from './BaseViewer.spec';
const viewer = new Viewer({
container: 'container'
});
testViewer(viewer);
const extendedViewer = new Viewer({
container: 'container',
alignToOrigin: false,
propertiesPanel: {
attachTo: '#properties-panel'
}
});
// typed API usage
type FooEvent = {
/**
* Very cool field!
*/
foo: string;
};
type EventMap = {
foo: FooEvent
};
type TypeMap = {
canvas: Canvas,
eventBus: EventBus
};
const typedViewer = new Viewer();
const bus = typedViewer.get('eventBus');
const canvas = typedViewer.get('canvas');
canvas.zoom('fit-viewport');
typedViewer.on('foo', event => {
console.log(event.foo);
});
typedViewer.get('eventBus').on('foo', e => console.log(e.foo));
================================================
FILE: lib/core/index.js
================================================
import DrawModule from '../draw';
import ImportModule from '../import';
export default {
__depends__: [
DrawModule,
ImportModule
]
};
================================================
FILE: lib/draw/BpmnRenderUtil.js
================================================
import {
has,
some
} from 'min-dash';
import {
getDi
} from '../util/ModelUtil';
import {
componentsToPath
} from 'diagram-js/lib/util/RenderUtil';
/**
* @typedef {import('../model').ModdleElement} ModdleElement
* @typedef {import('../model').Element} Element
*
* @typedef {import('../model').ShapeLike} ShapeLike
*
* @typedef {import('diagram-js/lib/util/Types').Dimensions} Dimensions
* @typedef {import('diagram-js/lib/util/Types').Rect} Rect
*/
// re-export for compatibility
export {
getDi,
getBusinessObject as getSemantic
} from '../util/ModelUtil';
export var black = 'hsl(225, 10%, 15%)';
export var white = 'white';
// element utils //////////////////////
/**
* Checks if eventDefinition of the given element matches with semantic type.
*
* @param {ModdleElement} event
* @param {string} eventDefinitionType
*
* @return {boolean}
*/
export function isTypedEvent(event, eventDefinitionType) {
return some(event.eventDefinitions, function(definition) {
return definition.$type === eventDefinitionType;
});
}
/**
* Check if element is a throw event.
*
* @param {ModdleElement} event
*
* @return {boolean}
*/
export function isThrowEvent(event) {
return (event.$type === 'bpmn:IntermediateThrowEvent') || (event.$type === 'bpmn:EndEvent');
}
/**
* Check if element is a throw event.
*
* @param {ModdleElement} element
*
* @return {boolean}
*/
export function isCollection(element) {
var dataObject = element.dataObjectRef;
return element.isCollection || (dataObject && dataObject.isCollection);
}
// color access //////////////////////
/**
* @param {Element} element
* @param {string} [defaultColor]
* @param {string} [overrideColor]
*
* @return {string}
*/
export function getFillColor(element, defaultColor, overrideColor) {
var di = getDi(element);
return overrideColor || di.get('color:background-color') || di.get('bioc:fill') || defaultColor || white;
}
/**
* @param {Element} element
* @param {string} [defaultColor]
* @param {string} [overrideColor]
*
* @return {string}
*/
export function getStrokeColor(element, defaultColor, overrideColor) {
var di = getDi(element);
return overrideColor || di.get('color:border-color') || di.get('bioc:stroke') || defaultColor || black;
}
/**
* @param {Element} element
* @param {string} [defaultColor]
* @param {string} [defaultStrokeColor]
* @param {string} [overrideColor]
*
* @return {string}
*/
export function getLabelColor(element, defaultColor, defaultStrokeColor, overrideColor) {
var di = getDi(element),
label = di.get('label');
return overrideColor || (label && label.get('color:color')) || defaultColor ||
getStrokeColor(element, defaultStrokeColor);
}
// cropping path customizations //////////////////////
/**
* @param {ShapeLike} shape
*
* @return {string} path
*/
export function getCirclePath(shape) {
var cx = shape.x + shape.width / 2,
cy = shape.y + shape.height / 2,
radius = shape.width / 2;
var circlePath = [
[ 'M', cx, cy ],
[ 'm', 0, -radius ],
[ 'a', radius, radius, 0, 1, 1, 0, 2 * radius ],
[ 'a', radius, radius, 0, 1, 1, 0, -2 * radius ],
[ 'z' ]
];
return componentsToPath(circlePath);
}
/**
* @param {ShapeLike} shape
* @param {number} [borderRadius]
*
* @return {string} path
*/
export function getRoundRectPath(shape, borderRadius) {
var x = shape.x,
y = shape.y,
width = shape.width,
height = shape.height;
var roundRectPath = [
[ 'M', x + borderRadius, y ],
[ 'l', width - borderRadius * 2, 0 ],
[ 'a', borderRadius, borderRadius, 0, 0, 1, borderRadius, borderRadius ],
[ 'l', 0, height - borderRadius * 2 ],
[ 'a', borderRadius, borderRadius, 0, 0, 1, -borderRadius, borderRadius ],
[ 'l', borderRadius * 2 - width, 0 ],
[ 'a', borderRadius, borderRadius, 0, 0, 1, -borderRadius, -borderRadius ],
[ 'l', 0, borderRadius * 2 - height ],
[ 'a', borderRadius, borderRadius, 0, 0, 1, borderRadius, -borderRadius ],
[ 'z' ]
];
return componentsToPath(roundRectPath);
}
/**
* @param {ShapeLike} shape
*
* @return {string} path
*/
export function getDiamondPath(shape) {
var width = shape.width,
height = shape.height,
x = shape.x,
y = shape.y,
halfWidth = width / 2,
halfHeight = height / 2;
var diamondPath = [
[ 'M', x + halfWidth, y ],
[ 'l', halfWidth, halfHeight ],
[ 'l', -halfWidth, halfHeight ],
[ 'l', -halfWidth, -halfHeight ],
[ 'z' ]
];
return componentsToPath(diamondPath);
}
/**
* @param {ShapeLike} shape
*
* @return {string} path
*/
export function getRectPath(shape) {
var x = shape.x,
y = shape.y,
width = shape.width,
height = shape.height;
var rectPath = [
[ 'M', x, y ],
[ 'l', width, 0 ],
[ 'l', 0, height ],
[ 'l', -width, 0 ],
[ 'z' ]
];
return componentsToPath(rectPath);
}
/**
* Get width and height from element or overrides.
*
* @param {Dimensions|Rect|ShapeLike} bounds
* @param {Object} overrides
*
* @returns {Dimensions}
*/
export function getBounds(bounds, overrides = {}) {
return {
width: getWidth(bounds, overrides),
height: getHeight(bounds, overrides)
};
}
/**
* Get width from element or overrides.
*
* @param {Dimensions|Rect|ShapeLike} bounds
* @param {Object} overrides
*
* @returns {number}
*/
export function getWidth(bounds, overrides = {}) {
return has(overrides, 'width') ? overrides.width : bounds.width;
}
/**
* Get height from element or overrides.
*
* @param {Dimensions|Rect|ShapeLike} bounds
* @param {Object} overrides
*
* @returns {number}
*/
export function getHeight(bounds, overrides = {}) {
return has(overrides, 'height') ? overrides.height : bounds.height;
}
================================================
FILE: lib/draw/BpmnRenderer.js
================================================
import inherits from 'inherits-browser';
import {
assign,
forEach,
isObject
} from 'min-dash';
import BaseRenderer from 'diagram-js/lib/draw/BaseRenderer';
import {
isExpanded,
isHorizontal,
isEventSubProcess
} from '../util/DiUtil';
import {
getLabel,
isLabel
} from '../util/LabelUtil';
import {
is
} from '../util/ModelUtil';
import {
createLine
} from 'diagram-js/lib/util/RenderUtil';
import {
isTypedEvent,
isThrowEvent,
isCollection,
getBounds,
getDi,
getSemantic,
getCirclePath,
getRoundRectPath,
getDiamondPath,
getRectPath,
getFillColor,
getStrokeColor,
getLabelColor,
getHeight,
getWidth
} from './BpmnRenderUtil';
import {
query as domQuery
} from 'min-dom';
import {
append as svgAppend,
attr as svgAttr,
create as svgCreate,
classes as svgClasses
} from 'tiny-svg';
import {
rotate,
transform,
translate
} from 'diagram-js/lib/util/SvgTransformUtil';
import { Ids } from 'ids';
import { black } from './BpmnRenderUtil';
var markerIds = new Ids();
var ELEMENT_LABEL_DISTANCE = 10,
INNER_OUTER_DIST = 3,
PARTICIPANT_STROKE_WIDTH = 1.5,
TASK_BORDER_RADIUS = 10,
EXTERNAL_LABEL_BORDER_RADIUS = 4;
var DEFAULT_OPACITY = 0.95,
FULL_OPACITY = 1,
LOW_OPACITY = 0.25;
/**
* @typedef { Partial<{
* defaultFillColor: string,
* defaultStrokeColor: string,
* defaultLabelColor: string
* }> } BpmnRendererConfig
*
* @typedef { Partial<{
* fill: string,
* stroke: string,
* width: string,
* height: string
* }> } Attrs
*/
/**
* @typedef { import('../model/Types').Element } Element
* @typedef { import('../model/Types').Shape } Shape
* @typedef { import('../model/Types').Connection } Connection
*/
/**
* A renderer for BPMN elements
*
* @param {BpmnRendererConfig} config
* @param {import('diagram-js/lib/core/EventBus').default} eventBus
* @param {import('diagram-js/lib/draw/Styles').default} styles
* @param {import('./PathMap').default} pathMap
* @param {import('diagram-js/lib/core/Canvas').default} canvas
* @param {import('./TextRenderer').default} textRenderer
* @param {number} [priority]
*/
export default function BpmnRenderer(
config, eventBus, styles, pathMap,
canvas, textRenderer, priority) {
BaseRenderer.call(this, eventBus, priority);
var defaultFillColor = config && config.defaultFillColor,
defaultStrokeColor = config && config.defaultStrokeColor,
defaultLabelColor = config && config.defaultLabelColor;
function shapeStyle(attrs) {
return styles.computeStyle(attrs, {
strokeLinecap: 'round',
strokeLinejoin: 'round',
stroke: black,
strokeWidth: 2,
fill: 'white'
});
}
function lineStyle(attrs) {
return styles.computeStyle(attrs, [ 'no-fill' ], {
strokeLinecap: 'round',
strokeLinejoin: 'round',
stroke: black,
strokeWidth: 2
});
}
function addMarker(id, options) {
var {
ref = { x: 0, y: 0 },
scale = 1,
element,
parentGfx = canvas._svg
} = options;
var marker = svgCreate('marker', {
id: id,
viewBox: '0 0 20 20',
refX: ref.x,
refY: ref.y,
markerWidth: 20 * scale,
markerHeight: 20 * scale,
orient: 'auto'
});
svgAppend(marker, element);
var defs = domQuery(':scope > defs', parentGfx);
if (!defs) {
defs = svgCreate('defs');
svgAppend(parentGfx, defs);
}
svgAppend(defs, marker);
}
function marker(parentGfx, type, fill, stroke) {
var id = markerIds.nextPrefixed('marker-');
createMarker(parentGfx, id, type, fill, stroke);
return 'url(#' + id + ')';
}
function createMarker(parentGfx, id, type, fill, stroke) {
if (type === 'sequenceflow-end') {
var sequenceflowEnd = svgCreate('path', {
d: 'M 1 5 L 11 10 L 1 15 Z',
...shapeStyle({
fill: stroke,
stroke: stroke,
strokeWidth: 1
})
});
addMarker(id, {
element: sequenceflowEnd,
ref: { x: 11, y: 10 },
scale: 0.5,
parentGfx
});
}
if (type === 'messageflow-start') {
var messageflowStart = svgCreate('circle', {
cx: 6,
cy: 6,
r: 3.5,
...shapeStyle({
fill,
stroke: stroke,
strokeWidth: 1,
// fix for safari / chrome / firefox bug not correctly
// resetting stroke dash array
strokeDasharray: [ 10000, 1 ]
})
});
addMarker(id, {
element: messageflowStart,
ref: { x: 6, y: 6 },
parentGfx
});
}
if (type === 'messageflow-end') {
var messageflowEnd = svgCreate('path', {
d: 'm 1 5 l 0 -3 l 7 3 l -7 3 z',
...shapeStyle({
fill,
stroke: stroke,
strokeWidth: 1,
// fix for safari / chrome / firefox bug not correctly
// resetting stroke dash array
strokeDasharray: [ 10000, 1 ]
})
});
addMarker(id, {
element: messageflowEnd,
ref: { x: 8.5, y: 5 },
parentGfx
});
}
if (type === 'association-start') {
var associationStart = svgCreate('path', {
d: 'M 11 5 L 1 10 L 11 15',
...lineStyle({
fill: 'none',
stroke,
strokeWidth: 1.5,
// fix for safari / chrome / firefox bug not correctly
// resetting stroke dash array
strokeDasharray: [ 10000, 1 ]
})
});
addMarker(id, {
element: associationStart,
ref: { x: 1, y: 10 },
scale: 0.5,
parentGfx
});
}
if (type === 'association-end') {
var associationEnd = svgCreate('path', {
d: 'M 1 5 L 11 10 L 1 15',
...lineStyle({
fill: 'none',
stroke,
strokeWidth: 1.5,
// fix for safari / chrome / firefox bug not correctly
// resetting stroke dash array
strokeDasharray: [ 10000, 1 ]
})
});
addMarker(id, {
element: associationEnd,
ref: { x: 11, y: 10 },
scale: 0.5,
parentGfx
});
}
if (type === 'conditional-flow-marker') {
var conditionalFlowMarker = svgCreate('path', {
d: 'M 0 10 L 8 6 L 16 10 L 8 14 Z',
...shapeStyle({
fill,
stroke: stroke
})
});
addMarker(id, {
element: conditionalFlowMarker,
ref: { x: -1, y: 10 },
scale: 0.5,
parentGfx
});
}
if (type === 'conditional-default-flow-marker') {
var defaultFlowMarker = svgCreate('path', {
d: 'M 6 4 L 10 16',
...shapeStyle({
stroke: stroke,
fill: 'none'
})
});
addMarker(id, {
element: defaultFlowMarker,
ref: { x: 0, y: 10 },
scale: 0.5,
parentGfx
});
}
}
function drawCircle(parentGfx, width, height, offset, attrs = {}) {
if (isObject(offset)) {
attrs = offset;
offset = 0;
}
offset = offset || 0;
attrs = shapeStyle(attrs);
var cx = width / 2,
cy = height / 2;
var circle = svgCreate('circle', {
cx: cx,
cy: cy,
r: Math.round((width + height) / 4 - offset),
...attrs
});
svgAppend(parentGfx, circle);
return circle;
}
function drawRect(parentGfx, width, height, r, offset, attrs) {
if (isObject(offset)) {
attrs = offset;
offset = 0;
}
offset = offset || 0;
attrs = shapeStyle(attrs);
var rect = svgCreate('rect', {
x: offset,
y: offset,
width: width - offset * 2,
height: height - offset * 2,
rx: r,
ry: r,
...attrs
});
svgAppend(parentGfx, rect);
return rect;
}
function drawDiamond(parentGfx, width, height, attrs) {
var x_2 = width / 2;
var y_2 = height / 2;
var points = [
{ x: x_2, y: 0 },
{ x: width, y: y_2 },
{ x: x_2, y: height },
{ x: 0, y: y_2 }
];
var pointsString = points.map(function(point) {
return point.x + ',' + point.y;
}).join(' ');
attrs = shapeStyle(attrs);
var polygon = svgCreate('polygon', {
...attrs,
points: pointsString
});
svgAppend(parentGfx, polygon);
return polygon;
}
/**
* @param {SVGElement} parentGfx
* @param {Point[]} waypoints
* @param {any} attrs
* @param {number} [radius]
*
* @return {SVGElement}
*/
function drawLine(parentGfx, waypoints, attrs, radius) {
attrs = lineStyle(attrs);
var line = createLine(waypoints, attrs, radius);
svgAppend(parentGfx, line);
return line;
}
/**
* @param {SVGElement} parentGfx
* @param {Point[]} waypoints
* @param {any} attrs
*
* @return {SVGElement}
*/
function drawConnectionSegments(parentGfx, waypoints, attrs) {
return drawLine(parentGfx, waypoints, attrs, 5);
}
function drawPath(parentGfx, d, attrs) {
attrs = lineStyle(attrs);
var path = svgCreate('path', {
...attrs,
d
});
svgAppend(parentGfx, path);
return path;
}
function drawMarker(type, parentGfx, path, attrs) {
return drawPath(parentGfx, path, assign({ 'data-marker': type }, attrs));
}
function renderer(type) {
return handlers[type];
}
function as(type) {
return function(parentGfx, element, attrs) {
return renderer(type)(parentGfx, element, attrs);
};
}
var eventIconRenderers = {
'bpmn:MessageEventDefinition': function(parentGfx, element, attrs = {}, isThrowing) {
var pathData = pathMap.getScaledPath('EVENT_MESSAGE', {
xScaleFactor: 0.9,
yScaleFactor: 0.9,
containerWidth: attrs.width || element.width,
containerHeight: attrs.height || element.height,
position: {
mx: 0.235,
my: 0.315
}
});
var fill = isThrowing
? getStrokeColor(element, defaultStrokeColor, attrs.stroke)
: getFillColor(element, defaultFillColor, attrs.fill);
var stroke = isThrowing
? getFillColor(element, defaultFillColor, attrs.fill)
: getStrokeColor(element, defaultStrokeColor, attrs.stroke);
var messagePath = drawPath(parentGfx, pathData, {
fill,
stroke,
strokeWidth: 1
});
return messagePath;
},
'bpmn:TimerEventDefinition': function(parentGfx, element, attrs = {}) {
var baseWidth = attrs.width || element.width;
var baseHeight = attrs.height || element.height;
// use a lighter stroke for event suprocess icons
var strokeWidth = attrs.width ? 1 : 2;
var circle = drawCircle(parentGfx, baseWidth, baseHeight, 0.2 * baseHeight, {
fill: getFillColor(element, defaultFillColor, attrs.fill),
stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke),
strokeWidth: strokeWidth
});
var pathData = pathMap.getScaledPath('EVENT_TIMER_WH', {
xScaleFactor: 0.75,
yScaleFactor: 0.75,
containerWidth: baseWidth,
containerHeight: baseHeight,
position: {
mx: 0.5,
my: 0.5
}
});
drawPath(parentGfx, pathData, {
stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke),
strokeWidth: strokeWidth
});
for (var i = 0; i < 12; i++) {
var linePathData = pathMap.getScaledPath('EVENT_TIMER_LINE', {
xScaleFactor: 0.75,
yScaleFactor: 0.75,
containerWidth: baseWidth,
containerHeight: baseHeight,
position: {
mx: 0.5,
my: 0.5
}
});
var width = baseWidth / 2,
height = baseHeight / 2;
drawPath(parentGfx, linePathData, {
strokeWidth: 1,
stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke),
transform: 'rotate(' + (i * 30) + ',' + height + ',' + width + ')'
});
}
return circle;
},
'bpmn:EscalationEventDefinition': function(parentGfx, event, attrs = {}, isThrowing) {
var pathData = pathMap.getScaledPath('EVENT_ESCALATION', {
xScaleFactor: 1,
yScaleFactor: 1,
containerWidth: attrs.width || event.width,
containerHeight: attrs.height || event.height,
position: {
mx: 0.5,
my: 0.2
}
});
var fill = isThrowing
? getStrokeColor(event, defaultStrokeColor, attrs.stroke)
: getFillColor(event, defaultFillColor, attrs.fill);
return drawPath(parentGfx, pathData, {
fill,
stroke: getStrokeColor(event, defaultStrokeColor, attrs.stroke),
strokeWidth: 1
});
},
'bpmn:ConditionalEventDefinition': function(parentGfx, event, attrs = {}) {
var pathData = pathMap.getScaledPath('EVENT_CONDITIONAL', {
xScaleFactor: 1,
yScaleFactor: 1,
containerWidth: attrs.width || event.width,
containerHeight: attrs.height || event.height,
position: {
mx: 0.5,
my: 0.222
}
});
return drawPath(parentGfx, pathData, {
fill: getFillColor(event, defaultFillColor, attrs.fill),
stroke: getStrokeColor(event, defaultStrokeColor, attrs.stroke),
strokeWidth: 1
});
},
'bpmn:LinkEventDefinition': function(parentGfx, event, attrs = {}, isThrowing) {
var pathData = pathMap.getScaledPath('EVENT_LINK', {
xScaleFactor: 1,
yScaleFactor: 1,
containerWidth: event.width,
containerHeight: event.height,
position: {
mx: 0.57,
my: 0.263
}
});
var fill = isThrowing
? getStrokeColor(event, defaultStrokeColor, attrs.stroke)
: getFillColor(event, defaultFillColor, attrs.fill);
return drawPath(parentGfx, pathData, {
fill,
stroke: getStrokeColor(event, defaultStrokeColor, attrs.stroke),
strokeWidth: 1
});
},
'bpmn:ErrorEventDefinition': function(parentGfx, event, attrs = {}, isThrowing) {
var pathData = pathMap.getScaledPath('EVENT_ERROR', {
xScaleFactor: 1.1,
yScaleFactor: 1.1,
containerWidth: attrs.width || event.width,
containerHeight: attrs.height || event.height,
position: {
mx: 0.2,
my: 0.722
}
});
var fill = isThrowing
? getStrokeColor(event, defaultStrokeColor, attrs.stroke)
: getFillColor(event, defaultFillColor, attrs.fill);
return drawPath(parentGfx, pathData, {
fill,
stroke: getStrokeColor(event, defaultStrokeColor, attrs.stroke),
strokeWidth: 1
});
},
'bpmn:CancelEventDefinition': function(parentGfx, event, attrs = {}, isThrowing) {
var pathData = pathMap.getScaledPath('EVENT_CANCEL_45', {
xScaleFactor: 1.0,
yScaleFactor: 1.0,
containerWidth: event.width,
containerHeight: event.height,
position: {
mx: 0.638,
my: -0.055
}
});
var fill = isThrowing ? getStrokeColor(event, defaultStrokeColor, attrs.stroke) : 'none';
var path = drawPath(parentGfx, pathData, {
fill,
stroke: getStrokeColor(event, defaultStrokeColor, attrs.stroke),
strokeWidth: 1
});
rotate(path, 45);
return path;
},
'bpmn:CompensateEventDefinition': function(parentGfx, event, attrs = {}, isThrowing) {
var pathData = pathMap.getScaledPath('EVENT_COMPENSATION', {
xScaleFactor: 1,
yScaleFactor: 1,
containerWidth: attrs.width || event.width,
containerHeight: attrs.height || event.height,
position: {
mx: 0.22,
my: 0.5
}
});
var fill = isThrowing
? getStrokeColor(event, defaultStrokeColor, attrs.stroke)
: getFillColor(event, defaultFillColor, attrs.fill);
return drawPath(parentGfx, pathData, {
fill,
stroke: getStrokeColor(event, defaultStrokeColor, attrs.stroke),
strokeWidth: 1
});
},
'bpmn:SignalEventDefinition': function(parentGfx, event, attrs = {}, isThrowing) {
var pathData = pathMap.getScaledPath('EVENT_SIGNAL', {
xScaleFactor: 0.9,
yScaleFactor: 0.9,
containerWidth: attrs.width || event.width,
containerHeight: attrs.height || event.height,
position: {
mx: 0.5,
my: 0.2
}
});
var fill = isThrowing
? getStrokeColor(event, defaultStrokeColor, attrs.stroke)
: getFillColor(event, defaultFillColor, attrs.fill);
return drawPath(parentGfx, pathData, {
strokeWidth: 1,
fill,
stroke: getStrokeColor(event, defaultStrokeColor, attrs.stroke)
});
},
'bpmn:MultipleEventDefinition': function(parentGfx, event, attrs = {}, isThrowing) {
var pathData = pathMap.getScaledPath('EVENT_MULTIPLE', {
xScaleFactor: 1.1,
yScaleFactor: 1.1,
containerWidth: attrs.width || event.width,
containerHeight: attrs.height || event.height,
position: {
mx: 0.211,
my: 0.36
}
});
var fill = isThrowing
? getStrokeColor(event, defaultStrokeColor, attrs.stroke)
: getFillColor(event, defaultFillColor, attrs.fill);
return drawPath(parentGfx, pathData, {
fill,
stroke: getStrokeColor(event, defaultStrokeColor, attrs.stroke),
strokeWidth: 1
});
},
'bpmn:ParallelMultipleEventDefinition': function(parentGfx, event, attrs = {}) {
var pathData = pathMap.getScaledPath('EVENT_PARALLEL_MULTIPLE', {
xScaleFactor: 1.2,
yScaleFactor: 1.2,
containerWidth: attrs.width || event.width,
containerHeight: attrs.height || event.height,
position: {
mx: 0.458,
my: 0.194
}
});
return drawPath(parentGfx, pathData, {
fill: getFillColor(event, defaultFillColor, attrs.fill),
stroke: getStrokeColor(event, defaultStrokeColor, attrs.stroke),
strokeWidth: 1
});
},
'bpmn:TerminateEventDefinition': function(parentGfx, element, attrs = {}) {
var circle = drawCircle(parentGfx, element.width, element.height, 8, {
fill: getStrokeColor(element, defaultStrokeColor, attrs.stroke),
stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke),
strokeWidth: 4
});
return circle;
}
};
function renderEventIcon(element, parentGfx, attrs = {}, proxyElement) {
var semantic = getSemantic(element),
isThrowing = isThrowEvent(semantic);
var nodeElement = proxyElement || element;
if (semantic.get('eventDefinitions') && semantic.get('eventDefinitions').length > 1) {
if (semantic.get('parallelMultiple')) {
return eventIconRenderers[ 'bpmn:ParallelMultipleEventDefinition' ](parentGfx, nodeElement, attrs, isThrowing);
}
else {
return eventIconRenderers[ 'bpmn:MultipleEventDefinition' ](parentGfx, nodeElement, attrs, isThrowing);
}
}
if (isTypedEvent(semantic, 'bpmn:MessageEventDefinition')) {
return eventIconRenderers[ 'bpmn:MessageEventDefinition' ](parentGfx, nodeElement, attrs, isThrowing);
}
if (isTypedEvent(semantic, 'bpmn:TimerEventDefinition')) {
return eventIconRenderers[ 'bpmn:TimerEventDefinition' ](parentGfx, nodeElement, attrs, isThrowing);
}
if (isTypedEvent(semantic, 'bpmn:ConditionalEventDefinition')) {
return eventIconRenderers[ 'bpmn:ConditionalEventDefinition' ](parentGfx, nodeElement, attrs, isThrowing);
}
if (isTypedEvent(semantic, 'bpmn:SignalEventDefinition')) {
return eventIconRenderers[ 'bpmn:SignalEventDefinition' ](parentGfx, nodeElement, attrs, isThrowing);
}
if (isTypedEvent(semantic, 'bpmn:EscalationEventDefinition')) {
return eventIconRenderers[ 'bpmn:EscalationEventDefinition' ](parentGfx, nodeElement, attrs, isThrowing);
}
if (isTypedEvent(semantic, 'bpmn:LinkEventDefinition')) {
return eventIconRenderers[ 'bpmn:LinkEventDefinition' ](parentGfx, nodeElement, attrs, isThrowing);
}
if (isTypedEvent(semantic, 'bpmn:ErrorEventDefinition')) {
return eventIconRenderers[ 'bpmn:ErrorEventDefinition' ](parentGfx, nodeElement, attrs, isThrowing);
}
if (isTypedEvent(semantic, 'bpmn:CancelEventDefinition')) {
return eventIconRenderers[ 'bpmn:CancelEventDefinition' ](parentGfx, nodeElement, attrs, isThrowing);
}
if (isTypedEvent(semantic, 'bpmn:CompensateEventDefinition')) {
return eventIconRenderers[ 'bpmn:CompensateEventDefinition' ](parentGfx, nodeElement, attrs, isThrowing);
}
if (isTypedEvent(semantic, 'bpmn:TerminateEventDefinition')) {
return eventIconRenderers[ 'bpmn:TerminateEventDefinition' ](parentGfx, nodeElement, attrs, isThrowing);
}
return null;
}
var taskMarkerRenderers = {
'ParticipantMultiplicityMarker': function(parentGfx, element, attrs = {}) {
var width = getWidth(element, attrs),
height = getHeight(element, attrs);
var markerPath = pathMap.getScaledPath('MARKER_PARALLEL', {
xScaleFactor: 1,
yScaleFactor: 1,
containerWidth: width,
containerHeight: height,
position: {
mx: ((width / 2 - 6) / width),
my: (height - 15) / height
}
});
drawMarker('participant-multiplicity', parentGfx, markerPath, {
strokeWidth: 2,
fill: getFillColor(element, defaultFillColor, attrs.fill),
stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke)
});
},
'SubProcessMarker': function(parentGfx, element, attrs = {}) {
var markerRect = drawRect(parentGfx, 14, 14, 0, {
strokeWidth: 1,
fill: getFillColor(element, defaultFillColor, attrs.fill),
stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke)
});
translate(markerRect, element.width / 2 - 7.5, element.height - 20);
var markerPath = pathMap.getScaledPath('MARKER_SUB_PROCESS', {
xScaleFactor: 1.5,
yScaleFactor: 1.5,
containerWidth: element.width,
containerHeight: element.height,
position: {
mx: (element.width / 2 - 7.5) / element.width,
my: (element.height - 20) / element.height
}
});
drawMarker('sub-process', parentGfx, markerPath, {
fill: getFillColor(element, defaultFillColor, attrs.fill),
stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke)
});
},
'ParallelMarker': function(parentGfx, element, attrs) {
var width = getWidth(element, attrs),
height = getHeight(element, attrs);
var markerPath = pathMap.getScaledPath('MARKER_PARALLEL', {
xScaleFactor: 1,
yScaleFactor: 1,
containerWidth: width,
containerHeight: height,
position: {
mx: ((width / 2 + attrs.parallel) / width),
my: (height - 20) / height
}
});
drawMarker('parallel', parentGfx, markerPath, {
fill: getFillColor(element, defaultFillColor, attrs.fill),
stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke)
});
},
'SequentialMarker': function(parentGfx, element, attrs) {
var markerPath = pathMap.getScaledPath('MARKER_SEQUENTIAL', {
xScaleFactor: 1,
yScaleFactor: 1,
containerWidth: element.width,
containerHeight: element.height,
position: {
mx: ((element.width / 2 + attrs.seq) / element.width),
my: (element.height - 19) / element.height
}
});
drawMarker('sequential', parentGfx, markerPath, {
fill: getFillColor(element, defaultFillColor, attrs.fill),
stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke)
});
},
'CompensationMarker': function(parentGfx, element, attrs) {
var markerMath = pathMap.getScaledPath('MARKER_COMPENSATION', {
xScaleFactor: 1,
yScaleFactor: 1,
containerWidth: element.width,
containerHeight: element.height,
position: {
mx: ((element.width / 2 + attrs.compensation) / element.width),
my: (element.height - 13) / element.height
}
});
drawMarker('compensation', parentGfx, markerMath, {
strokeWidth: 1,
fill: getFillColor(element, defaultFillColor, attrs.fill),
stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke)
});
},
'LoopMarker': function(parentGfx, element, attrs) {
var width = getWidth(element, attrs),
height = getHeight(element, attrs);
var markerPath = pathMap.getScaledPath('MARKER_LOOP', {
xScaleFactor: 1,
yScaleFactor: 1,
containerWidth: width,
containerHeight: height,
position: {
mx: ((width / 2 + attrs.loop) / width),
my: (height - 7) / height
}
});
drawMarker('loop', parentGfx, markerPath, {
strokeWidth: 1.5,
fill: 'none',
stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke),
strokeMiterlimit: 0.5
});
},
'AdhocMarker': function(parentGfx, element, attrs) {
var width = getWidth(element, attrs),
height = getHeight(element, attrs);
var markerPath = pathMap.getScaledPath('MARKER_ADHOC', {
xScaleFactor: 1,
yScaleFactor: 1,
containerWidth: width,
containerHeight: height,
position: {
mx: ((width / 2 + attrs.adhoc) / width),
my: (height - 15) / height
}
});
drawMarker('adhoc', parentGfx, markerPath, {
strokeWidth: 1,
fill: getStrokeColor(element, defaultStrokeColor, attrs.stroke),
stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke)
});
}
};
function renderTaskMarker(type, parentGfx, element, attrs) {
taskMarkerRenderers[ type ](parentGfx, element, attrs);
}
function renderTaskMarkers(parentGfx, element, taskMarkers = [], attrs = {}) {
attrs = {
fill: attrs.fill,
stroke: attrs.stroke,
width: getWidth(element, attrs),
height: getHeight(element, attrs)
};
var semantic = getSemantic(element);
var subprocess = taskMarkers.includes('SubProcessMarker');
if (subprocess) {
attrs = {
...attrs,
seq: -21,
parallel: -22,
compensation: -25,
loop: -18,
adhoc: 10
};
} else {
attrs = {
...attrs,
seq: -5,
parallel: -6,
compensation: -7,
loop: 0,
adhoc: -8
};
}
if (semantic.get('isForCompensation')) {
taskMarkers.push('CompensationMarker');
}
if (is(semantic, 'bpmn:AdHocSubProcess')) {
taskMarkers.push('AdhocMarker');
if (!subprocess) {
assign(attrs, { compensation: attrs.compensation - 18 });
}
}
var loopCharacteristics = semantic.get('loopCharacteristics'),
isSequential = loopCharacteristics && loopCharacteristics.get('isSequential');
if (loopCharacteristics) {
assign(attrs, {
compensation: attrs.compensation - 18,
});
if (taskMarkers.includes('AdhocMarker')) {
assign(attrs, {
seq: -23,
loop: -18,
parallel: -24
});
}
if (isSequential === undefined) {
taskMarkers.push('LoopMarker');
}
if (isSequential === false) {
taskMarkers.push('ParallelMarker');
}
if (isSequential === true) {
taskMarkers.push('SequentialMarker');
}
}
if (taskMarkers.includes('CompensationMarker') && taskMarkers.length === 1) {
assign(attrs, {
compensation: -8
});
}
forEach(taskMarkers, function(marker) {
renderTaskMarker(marker, parentGfx, element, attrs);
});
}
function renderLabel(parentGfx, label, attrs = {}) {
attrs = assign({
size: {
width: 100
}
}, attrs);
var text = textRenderer.createText(label || '', attrs);
svgClasses(text).add('djs-label');
svgAppend(parentGfx, text);
return text;
}
function renderEmbeddedLabel(parentGfx, element, align, attrs = {}) {
var semantic = getSemantic(element);
var box = getBounds({
x: element.x,
y: element.y,
width: element.width,
height: element.height
}, attrs);
return renderLabel(parentGfx, semantic.name, {
align,
box,
padding: 7,
style: {
fill: getLabelColor(element, defaultLabelColor, defaultStrokeColor, attrs.stroke)
}
});
}
function renderExternalLabel(parentGfx, element, attrs = {}) {
var box = {
width: 90,
height: 30,
x: element.width / 2 + element.x,
y: element.height / 2 + element.y
};
return renderLabel(parentGfx, getLabel(element), {
box: box,
fitBox: true,
style: assign(
{},
textRenderer.getExternalStyle(),
{
fill: getLabelColor(element, defaultLabelColor, defaultStrokeColor, attrs.stroke)
}
)
});
}
function renderLaneLabel(parentGfx, text, element, attrs = {}) {
var isHorizontalLane = isHorizontal(element);
var textBox = renderLabel(parentGfx, text, {
box: {
height: 30,
width: isHorizontalLane ? getHeight(element, attrs) : getWidth(element, attrs),
},
align: 'center-middle',
style: {
fill: getLabelColor(element, defaultLabelColor, defaultStrokeColor, attrs.stroke)
}
});
if (isHorizontalLane) {
var top = -1 * getHeight(element, attrs);
transform(textBox, 0, -top, 270);
}
}
function renderActivity(parentGfx, element, attrs = {}) {
var {
width,
height
} = getBounds(element, attrs);
return drawRect(parentGfx, width, height, TASK_BORDER_RADIUS, {
...attrs,
fill: getFillColor(element, defaultFillColor, attrs.fill),
fillOpacity: DEFAULT_OPACITY,
stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke)
});
}
function renderAssociation(parentGfx, element, attrs = {}) {
var semantic = getSemantic(element);
var fill = getFillColor(element, defaultFillColor, attrs.fill),
stroke = getStrokeColor(element, defaultStrokeColor, attrs.stroke);
if (semantic.get('associationDirection') === 'One' ||
semantic.get('associationDirection') === 'Both') {
attrs.markerEnd = marker(parentGfx, 'association-end', fill, stroke);
}
if (semantic.get('associationDirection') === 'Both') {
attrs.markerStart = marker(parentGfx, 'association-start', fill, stroke);
}
attrs = pickAttrs(attrs, [
'markerStart',
'markerEnd'
]);
return drawConnectionSegments(parentGfx, element.waypoints, {
...attrs,
stroke,
strokeDasharray: '0, 5'
});
}
function renderDataObject(parentGfx, element, attrs = {}) {
var fill = getFillColor(element, defaultFillColor, attrs.fill),
stroke = getStrokeColor(element, defaultStrokeColor, attrs.stroke);
var pathData = pathMap.getScaledPath('DATA_OBJECT_PATH', {
xScaleFactor: 1,
yScaleFactor: 1,
containerWidth: element.width,
containerHeight: element.height,
position: {
mx: 0.474,
my: 0.296
}
});
var dataObject = drawPath(parentGfx, pathData, {
fill,
fillOpacity: DEFAULT_OPACITY,
stroke
});
var semantic = getSemantic(element);
if (isCollection(semantic)) {
var collectionPathData = pathMap.getScaledPath('DATA_OBJECT_COLLECTION_PATH', {
xScaleFactor: 1,
yScaleFactor: 1,
containerWidth: element.width,
containerHeight: element.height,
position: {
mx: 0.33,
my: (element.height - 18) / element.height
}
});
drawPath(parentGfx, collectionPathData, {
strokeWidth: 2,
fill,
stroke
});
}
return dataObject;
}
function renderEvent(parentGfx, element, attrs = {}) {
return drawCircle(parentGfx, element.width, element.height, {
fillOpacity: DEFAULT_OPACITY,
...attrs,
fill: getFillColor(element, defaultFillColor, attrs.fill),
stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke)
});
}
function renderGateway(parentGfx, element, attrs = {}) {
return drawDiamond(parentGfx, element.width, element.height, {
fill: getFillColor(element, defaultFillColor, attrs.fill),
fillOpacity: DEFAULT_OPACITY,
stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke)
});
}
function renderLane(parentGfx, element, attrs = {}) {
var lane = drawRect(parentGfx, getWidth(element, attrs), getHeight(element, attrs), 0, {
fill: getFillColor(element, defaultFillColor, attrs.fill),
fillOpacity: attrs.fillOpacity || DEFAULT_OPACITY,
stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke),
strokeWidth: 1.5
});
var semantic = getSemantic(element);
if (is(semantic, 'bpmn:Lane')) {
var text = semantic.get('name');
renderLaneLabel(parentGfx, text, element, attrs);
}
return lane;
}
function renderSubProcess(parentGfx, element, attrs = {}) {
var activity = renderActivity(parentGfx, element, attrs);
var expanded = isExpanded(element);
if (isEventSubProcess(element)) {
svgAttr(activity, {
strokeDasharray: '0, 5.5',
strokeWidth: 2.5
});
if (!expanded) {
var flowElements = getSemantic(element).flowElements || [];
var startEvents = flowElements.filter(e => is(e, 'bpmn:StartEvent'));
if (startEvents.length === 1) {
renderEventSubProcessIcon(startEvents[0], parentGfx, attrs, element);
}
}
}
renderEmbeddedLabel(parentGfx, element, expanded ? 'center-top' : 'center-middle', attrs);
if (expanded) {
renderTaskMarkers(parentGfx, element, undefined, attrs);
} else {
renderTaskMarkers(parentGfx, element, [ 'SubProcessMarker' ], attrs);
}
return activity;
}
function renderEventSubProcessIcon(startEvent, parentGfx, attrs, proxyElement) {
var iconSize = 22;
// match the colors of the enclosing subprocess
var proxyAttrs = {
fill: getFillColor(proxyElement, defaultFillColor, attrs.fill),
stroke: getStrokeColor(proxyElement, defaultStrokeColor, attrs.stroke),
width: iconSize,
height: iconSize
};
var interrupting = getSemantic(startEvent).isInterrupting;
var strokeDasharray = interrupting ? 0 : 3;
// better visibility for non-interrupting events
var strokeWidth = interrupting ? 1 : 1.2;
// make the icon look larger by drawing a smaller circle
var circleSize = 20;
var shift = (iconSize - circleSize) / 2;
var transform = 'translate(' + shift + ',' + shift + ')';
drawCircle(parentGfx, circleSize, circleSize, {
fill: proxyAttrs.fill,
stroke: proxyAttrs.stroke,
strokeWidth,
strokeDasharray,
transform
});
renderEventIcon(startEvent, parentGfx, proxyAttrs, proxyElement);
}
function renderTask(parentGfx, element, attrs = {}) {
var activity = renderActivity(parentGfx, element, attrs);
renderEmbeddedLabel(parentGfx, element, 'center-middle', attrs);
renderTaskMarkers(parentGfx, element, undefined, attrs);
return activity;
}
var handlers = this.handlers = {
'bpmn:AdHocSubProcess': function(parentGfx, element, attrs = {}) {
if (isExpanded(element)) {
attrs = pickAttrs(attrs, [
'fill',
'stroke',
'width',
'height'
]);
} else {
attrs = pickAttrs(attrs, [
'fill',
'stroke'
]);
}
return renderSubProcess(parentGfx, element, attrs);
},
'bpmn:Association': function(parentGfx, element, attrs = {}) {
attrs = pickAttrs(attrs, [
'fill',
'stroke'
]);
return renderAssociation(parentGfx, element, attrs);
},
'bpmn:BoundaryEvent': function(parentGfx, element, attrs = {}) {
var { renderIcon = true } = attrs;
attrs = pickAttrs(attrs, [
'fill',
'stroke'
]);
var semantic = getSemantic(element),
cancelActivity = semantic.get('cancelActivity');
attrs = {
strokeWidth: 1.5,
fill: getFillColor(element, defaultFillColor, attrs.fill),
fillOpacity: FULL_OPACITY,
stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke)
};
if (!cancelActivity) {
attrs.strokeDasharray = '6';
}
var event = renderEvent(parentGfx, element, attrs);
drawCircle(parentGfx, element.width, element.height, INNER_OUTER_DIST, {
...attrs,
fill: 'none'
});
if (renderIcon) {
renderEventIcon(element, parentGfx, attrs);
}
return event;
},
'bpmn:BusinessRuleTask': function(parentGfx, element, attrs = {}) {
attrs = pickAttrs(attrs, [
'fill',
'stroke'
]);
var task = renderTask(parentGfx, element, attrs);
var headerData = pathMap.getScaledPath('TASK_TYPE_BUSINESS_RULE_MAIN', {
abspos: {
x: 8,
y: 8
}
});
var businessPath = drawPath(parentGfx, headerData);
svgAttr(businessPath, {
fill: getFillColor(element, defaultFillColor, attrs.fill),
stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke),
strokeWidth: 1
});
var headerPathData = pathMap.getScaledPath('TASK_TYPE_BUSINESS_RULE_HEADER', {
abspos: {
x: 8,
y: 8
}
});
var businessHeaderPath = drawPath(parentGfx, headerPathData);
svgAttr(businessHeaderPath, {
fill: getStrokeColor(element, defaultStrokeColor, attrs.stroke),
stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke),
strokeWidth: 1
});
return task;
},
'bpmn:CallActivity': function(parentGfx, element, attrs = {}) {
attrs = pickAttrs(attrs, [
'fill',
'stroke'
]);
return renderSubProcess(parentGfx, element, {
strokeWidth: 5,
...attrs
});
},
'bpmn:ComplexGateway': function(parentGfx, element, attrs = {}) {
attrs = pickAttrs(attrs, [
'fill',
'stroke'
]);
var gateway = renderGateway(parentGfx, element, attrs);
var pathData = pathMap.getScaledPath('GATEWAY_COMPLEX', {
xScaleFactor: 0.5,
yScaleFactor:0.5,
containerWidth: element.width,
containerHeight: element.height,
position: {
mx: 0.46,
my: 0.26
}
});
drawPath(parentGfx, pathData, {
fill: getStrokeColor(element, defaultStrokeColor, attrs.stroke),
stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke),
strokeWidth: 1
});
return gateway;
},
'bpmn:DataInput': function(parentGfx, element, attrs = {}) {
attrs = pickAttrs(attrs, [
'fill',
'stroke'
]);
var arrowPathData = pathMap.getRawPath('DATA_ARROW');
var dataObject = renderDataObject(parentGfx, element, attrs);
drawPath(parentGfx, arrowPathData, {
fill: 'none',
stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke),
strokeWidth: 1
});
return dataObject;
},
'bpmn:DataInputAssociation': function(parentGfx, element, attrs = {}) {
attrs = pickAttrs(attrs, [
'fill',
'stroke'
]);
return renderAssociation(parentGfx, element, {
...attrs,
markerEnd: marker(parentGfx, 'association-end', getFillColor(element, defaultFillColor, attrs.fill), getStrokeColor(element, defaultStrokeColor, attrs.stroke))
});
},
'bpmn:DataObject': function(parentGfx, element, attrs = {}) {
attrs = pickAttrs(attrs, [
'fill',
'stroke'
]);
return renderDataObject(parentGfx, element, attrs);
},
'bpmn:DataObjectReference': as('bpmn:DataObject'),
'bpmn:DataOutput': function(parentGfx, element, attrs = {}) {
attrs = pickAttrs(attrs, [
'fill',
'stroke'
]);
var arrowPathData = pathMap.getRawPath('DATA_ARROW');
var dataObject = renderDataObject(parentGfx, element, attrs);
drawPath(parentGfx, arrowPathData, {
strokeWidth: 1,
fill: getFillColor(element, defaultFillColor, attrs.fill),
stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke)
});
return dataObject;
},
'bpmn:DataOutputAssociation': function(parentGfx, element, attrs = {}) {
attrs = pickAttrs(attrs, [
'fill',
'stroke'
]);
return renderAssociation(parentGfx, element, {
...attrs,
markerEnd: marker(parentGfx, 'association-end', getFillColor(element, defaultFillColor, attrs.fill), getStrokeColor(element, defaultStrokeColor, attrs.stroke))
});
},
'bpmn:DataStoreReference': function(parentGfx, element, attrs = {}) {
attrs = pickAttrs(attrs, [
'fill',
'stroke'
]);
var dataStorePath = pathMap.getScaledPath('DATA_STORE', {
xScaleFactor: 1,
yScaleFactor: 1,
containerWidth: element.width,
containerHeight: element.height,
position: {
mx: 0,
my: 0.133
}
});
return drawPath(parentGfx, dataStorePath, {
fill: getFillColor(element, defaultFillColor, attrs.fill),
fillOpacity: DEFAULT_OPACITY,
stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke),
strokeWidth: 2
});
},
'bpmn:EndEvent': function(parentGfx, element, attrs = {}) {
var { renderIcon = true } = attrs;
attrs = pickAttrs(attrs, [
'fill',
'stroke'
]);
var event = renderEvent(parentGfx, element, {
...attrs,
strokeWidth: 4
});
if (renderIcon) {
renderEventIcon(element, parentGfx, attrs);
}
return event;
},
'bpmn:EventBasedGateway': function(parentGfx, element, attrs = {}) {
attrs = pickAttrs(attrs, [
'fill',
'stroke'
]);
var semantic = getSemantic(element);
var diamond = renderGateway(parentGfx, element, attrs);
drawCircle(parentGfx, element.width, element.height, element.height * 0.20, {
fill: getFillColor(element, 'none', attrs.fill),
stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke),
strokeWidth: 1
});
var type = semantic.get('eventGatewayType'),
instantiate = !!semantic.get('instantiate');
function drawEvent() {
var pathData = pathMap.getScaledPath('GATEWAY_EVENT_BASED', {
xScaleFactor: 0.18,
yScaleFactor: 0.18,
containerWidth: element.width,
containerHeight: element.height,
position: {
mx: 0.36,
my: 0.44
}
});
drawPath(parentGfx, pathData, {
fill: 'none',
stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke),
strokeWidth: 2
});
}
if (type === 'Parallel') {
var pathData = pathMap.getScaledPath('GATEWAY_PARALLEL', {
xScaleFactor: 0.4,
yScaleFactor: 0.4,
containerWidth: element.width,
containerHeight: element.height,
position: {
mx: 0.474,
my: 0.296
}
});
drawPath(parentGfx, pathData, {
fill: 'none',
stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke),
strokeWidth: 1
});
} else if (type === 'Exclusive') {
if (!instantiate) {
drawCircle(parentGfx, element.width, element.height, element.height * 0.26, {
fill: 'none',
stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke),
strokeWidth: 1
});
}
drawEvent();
}
return diamond;
},
'bpmn:ExclusiveGateway': function(parentGfx, element, attrs = {}) {
attrs = pickAttrs(attrs, [
'fill',
'stroke'
]);
var gateway = renderGateway(parentGfx, element, attrs);
var pathData = pathMap.getScaledPath('GATEWAY_EXCLUSIVE', {
xScaleFactor: 0.4,
yScaleFactor: 0.4,
containerWidth: element.width,
containerHeight: element.height,
position: {
mx: 0.32,
my: 0.3
}
});
var di = getDi(element);
if (di.get('isMarkerVisible')) {
drawPath(parentGfx, pathData, {
fill: getStrokeColor(element, defaultStrokeColor, attrs.stroke),
stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke),
strokeWidth: 1
});
}
return gateway;
},
'bpmn:Gateway': function(parentGfx, element, attrs = {}) {
attrs = pickAttrs(attrs, [
'fill',
'stroke'
]);
return renderGateway(parentGfx, element, attrs);
},
'bpmn:Group': function(parentGfx, element, attrs = {}) {
attrs = pickAttrs(attrs, [
'fill',
'stroke',
'width',
'height'
]);
return drawRect(parentGfx, element.width, element.height, TASK_BORDER_RADIUS, {
stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke),
strokeWidth: 1.5,
strokeDasharray: '10, 6, 0, 6',
fill: 'none',
pointerEvents: 'none',
width: getWidth(element, attrs),
height: getHeight(element, attrs)
});
},
'bpmn:InclusiveGateway': function(parentGfx, element, attrs = {}) {
attrs = pickAttrs(attrs, [
'fill',
'stroke'
]);
var gateway = renderGateway(parentGfx, element, attrs);
drawCircle(parentGfx, element.width, element.height, element.height * 0.24, {
fill: getFillColor(element, defaultFillColor, attrs.fill),
stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke),
strokeWidth: 2.5
});
return gateway;
},
'bpmn:IntermediateEvent': function(parentGfx, element, attrs = {}) {
var { renderIcon = true } = attrs;
attrs = pickAttrs(attrs, [
'fill',
'stroke'
]);
var outer = renderEvent(parentGfx, element, {
...attrs,
strokeWidth: 1.5
});
drawCircle(parentGfx, element.width, element.height, INNER_OUTER_DIST, {
fill: 'none',
stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke),
strokeWidth: 1.5
});
if (renderIcon) {
renderEventIcon(element, parentGfx, attrs);
}
return outer;
},
'bpmn:IntermediateCatchEvent': as('bpmn:IntermediateEvent'),
'bpmn:IntermediateThrowEvent': as('bpmn:IntermediateEvent'),
'bpmn:Lane': function(parentGfx, element, attrs = {}) {
attrs = pickAttrs(attrs, [
'fill',
'stroke',
'width',
'height'
]);
return renderLane(parentGfx, element, {
...attrs,
fillOpacity: LOW_OPACITY
});
},
'bpmn:ManualTask': function(parentGfx, element, attrs = {}) {
attrs = pickAttrs(attrs, [
'fill',
'stroke'
]);
var task = renderTask(parentGfx, element, attrs);
var pathData = pathMap.getScaledPath('TASK_TYPE_MANUAL', {
abspos: {
x: 17,
y: 15
}
});
drawPath(parentGfx, pathData, {
fill: getFillColor(element, defaultFillColor, attrs.fill),
stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke),
strokeWidth: 0.5
});
return task;
},
'bpmn:MessageFlow': function(parentGfx, element, attrs = {}) {
attrs = pickAttrs(attrs, [
'fill',
'stroke'
]);
var semantic = getSemantic(element),
di = getDi(element);
var fill = getFillColor(element, defaultFillColor, attrs.fill),
stroke = getStrokeColor(element, defaultStrokeColor, attrs.stroke);
var path = drawConnectionSegments(parentGfx, element.waypoints, {
markerEnd: marker(parentGfx, 'messageflow-end', fill, stroke),
markerStart: marker(parentGfx, 'messageflow-start', fill, stroke),
stroke,
strokeDasharray: '10, 11',
strokeWidth: 1.5
});
if (semantic.get('messageRef')) {
var midPoint = path.getPointAtLength(path.getTotalLength() / 2);
var markerPathData = pathMap.getScaledPath('MESSAGE_FLOW_MARKER', {
abspos: {
x: midPoint.x,
y: midPoint.y
}
});
var messageAttrs = {
strokeWidth: 1
};
if (di.get('messageVisibleKind') === 'initiating') {
messageAttrs.fill = fill;
messageAttrs.stroke = stroke;
} else {
messageAttrs.fill = stroke;
messageAttrs.stroke = fill;
}
var message = drawPath(parentGfx, markerPathData, messageAttrs);
var messageRef = semantic.get('messageRef'),
name = messageRef.get('name');
var label = renderLabel(parentGfx, name, {
align: 'center-top',
fitBox: true,
style: {
fill: stroke
}
});
var messageBounds = message.getBBox(),
labelBounds = label.getBBox();
var translateX = midPoint.x - labelBounds.width / 2,
translateY = midPoint.y + messageBounds.height / 2 + ELEMENT_LABEL_DISTANCE;
transform(label, translateX, translateY, 0);
}
return path;
},
'bpmn:ParallelGateway': function(parentGfx, element, attrs = {}) {
attrs = pickAttrs(attrs, [
'fill',
'stroke'
]);
var diamond = renderGateway(parentGfx, element, attrs);
var pathData = pathMap.getScaledPath('GATEWAY_PARALLEL', {
xScaleFactor: 0.6,
yScaleFactor: 0.6,
containerWidth: element.width,
containerHeight: element.height,
position: {
mx: 0.46,
my: 0.2
}
});
drawPath(parentGfx, pathData, {
fill: getStrokeColor(element, defaultStrokeColor, attrs.stroke),
stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke),
strokeWidth: 1
});
return diamond;
},
'bpmn:Participant': function(parentGfx, element, attrs = {}) {
attrs = pickAttrs(attrs, [
'fill',
'stroke',
'width',
'height'
]);
var participant = renderLane(parentGfx, element, attrs);
var expandedParticipant = isExpanded(element);
var horizontalParticipant = isHorizontal(element);
var semantic = getSemantic(element),
name = semantic.get('name');
if (expandedParticipant) {
var waypoints = horizontalParticipant ? [
{
x: 30,
y: 0
},
{
x: 30,
y: getHeight(element, attrs)
}
] : [
{
x: 0,
y: 30
},
{
x: getWidth(element, attrs),
y: 30
}
];
drawLine(parentGfx, waypoints, {
stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke),
strokeWidth: PARTICIPANT_STROKE_WIDTH
});
renderLaneLabel(parentGfx, name, element, attrs);
} else {
var bounds = getBounds(element, attrs);
if (!horizontalParticipant) {
bounds.height = getWidth(element, attrs);
bounds.width = getHeight(element, attrs);
}
var textBox = renderLabel(parentGfx, name, {
box: bounds,
align: 'center-middle',
style: {
fill: getLabelColor(element, defaultLabelColor, defaultStrokeColor, attrs.stroke)
}
});
if (!horizontalParticipant) {
var top = -1 * getHeight(element, attrs);
transform(textBox, 0, -top, 270);
}
}
if (semantic.get('participantMultiplicity')) {
renderTaskMarker('ParticipantMultiplicityMarker', parentGfx, element, attrs);
}
return participant;
},
'bpmn:ReceiveTask' : function(parentGfx, element, attrs = {}) {
attrs = pickAttrs(attrs, [
'fill',
'stroke'
]);
var semantic = getSemantic(element);
var task = renderTask(parentGfx, element, attrs);
var pathData;
if (semantic.get('instantiate')) {
drawCircle(parentGfx, 28, 28, 20 * 0.22, {
fill: getFillColor(element, defaultFillColor, attrs.fill),
stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke),
strokeWidth: 1
});
pathData = pathMap.getScaledPath('TASK_TYPE_INSTANTIATING_SEND', {
abspos: {
x: 7.77,
y: 9.52
}
});
} else {
pathData = pathMap.getScaledPath('TASK_TYPE_SEND', {
xScaleFactor: 0.9,
yScaleFactor: 0.9,
containerWidth: 21,
containerHeight: 14,
position: {
mx: 0.3,
my: 0.4
}
});
}
drawPath(parentGfx, pathData, {
fill: getFillColor(element, defaultFillColor, attrs.fill),
stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke),
strokeWidth: 1
});
return task;
},
'bpmn:ScriptTask': function(parentGfx, element, attrs = {}) {
attrs = pickAttrs(attrs, [
'fill',
'stroke'
]);
var task = renderTask(parentGfx, element, attrs);
var pathData = pathMap.getScaledPath('TASK_TYPE_SCRIPT', {
abspos: {
x: 15,
y: 20
}
});
drawPath(parentGfx, pathData, {
fill: getFillColor(element, defaultFillColor, attrs.fill),
stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke),
strokeWidth: 1
});
return task;
},
'bpmn:SendTask': function(parentGfx, element, attrs = {}) {
attrs = pickAttrs(attrs, [
'fill',
'stroke'
]);
var task = renderTask(parentGfx, element, attrs);
var pathData = pathMap.getScaledPath('TASK_TYPE_SEND', {
xScaleFactor: 1,
yScaleFactor: 1,
containerWidth: 21,
containerHeight: 14,
position: {
mx: 0.285,
my: 0.357
}
});
drawPath(parentGfx, pathData, {
fill: getStrokeColor(element, defaultStrokeColor, attrs.stroke),
stroke: getFillColor(element, defaultFillColor, attrs.fill),
strokeWidth: 1
});
return task;
},
'bpmn:SequenceFlow': function(parentGfx, element, attrs = {}) {
attrs = pickAttrs(attrs, [
'fill',
'stroke'
]);
var fill = getFillColor(element, defaultFillColor, attrs.fill),
stroke = getStrokeColor(element, defaultStrokeColor, attrs.stroke);
var connection = drawConnectionSegments(parentGfx, element.waypoints, {
markerEnd: marker(parentGfx, 'sequenceflow-end', fill, stroke),
stroke
});
var semantic = getSemantic(element);
var { source } = element;
if (source) {
var sourceSemantic = getSemantic(source);
// conditional flow marker
if (semantic.get('conditionExpression') && is(sourceSemantic, 'bpmn:Activity')) {
svgAttr(connection, {
markerStart: marker(parentGfx, 'conditional-flow-marker', fill, stroke)
});
}
// default marker
if (sourceSemantic.get('default') && (is(sourceSemantic, 'bpmn:Gateway') || is(sourceSemantic, 'bpmn:Activity')) &&
sourceSemantic.get('default') === semantic) {
svgAttr(connection, {
markerStart: marker(parentGfx, 'conditional-default-flow-marker', fill, stroke)
});
}
}
return connection;
},
'bpmn:ServiceTask': function(parentGfx, element, attrs = {}) {
attrs = pickAttrs(attrs, [
'fill',
'stroke'
]);
var task = renderTask(parentGfx, element, attrs);
drawCircle(parentGfx, 10, 10, {
fill: getFillColor(element, defaultFillColor, attrs.fill),
stroke: 'none',
transform: 'translate(6, 6)'
});
var pathDataService1 = pathMap.getScaledPath('TASK_TYPE_SERVICE', {
abspos: {
x: 12,
y: 18
}
});
drawPath(parentGfx, pathDataService1, {
fill: getFillColor(element, defaultFillColor, attrs.fill),
stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke),
strokeWidth: 1
});
drawCircle(parentGfx, 10, 10, {
fill: getFillColor(element, defaultFillColor, attrs.fill),
stroke: 'none',
transform: 'translate(11, 10)'
});
var pathDataService2 = pathMap.getScaledPath('TASK_TYPE_SERVICE', {
abspos: {
x: 17,
y: 22
}
});
drawPath(parentGfx, pathDataService2, {
fill: getFillColor(element, defaultFillColor, attrs.fill),
stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke),
strokeWidth: 1
});
return task;
},
'bpmn:StartEvent': function(parentGfx, element, attrs = {}) {
var { renderIcon = true } = attrs;
attrs = pickAttrs(attrs, [
'fill',
'stroke'
]);
var semantic = getSemantic(element);
if (!semantic.get('isInterrupting')) {
attrs = {
...attrs,
strokeDasharray: '6'
};
}
var event = renderEvent(parentGfx, element, attrs);
if (renderIcon) {
renderEventIcon(element, parentGfx, attrs);
}
return event;
},
'bpmn:SubProcess': function(parentGfx, element, attrs = {}) {
if (isExpanded(element)) {
attrs = pickAttrs(attrs, [
'fill',
'stroke',
'width',
'height'
]);
} else {
attrs = pickAttrs(attrs, [
'fill',
'stroke'
]);
}
return renderSubProcess(parentGfx, element, attrs);
},
'bpmn:Task': function(parentGfx, element, attrs = {}) {
attrs = pickAttrs(attrs, [
'fill',
'stroke'
]);
return renderTask(parentGfx, element, attrs);
},
'bpmn:TextAnnotation': function(parentGfx, element, attrs = {}) {
attrs = pickAttrs(attrs, [
'fill',
'stroke',
'width',
'height'
]);
var {
width,
height
} = getBounds(element, attrs);
var textElement = drawRect(parentGfx, width, height, 0, 0, {
fill: 'none',
stroke: 'none'
});
var textPathData = pathMap.getScaledPath('TEXT_ANNOTATION', {
xScaleFactor: 1,
yScaleFactor: 1,
containerWidth: width,
containerHeight: height,
position: {
mx: 0.0,
my: 0.0
}
});
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 = {}) {
if (isExpanded(element)) {
attrs = pickAttrs(attrs, [
'fill',
'stroke',
'width',
'height'
]);
} else {
attrs = pickAttrs(attrs, [
'fill',
'stroke'
]);
}
var outer = renderSubProcess(parentGfx, element, {
strokeWidth: 1.5,
...attrs
});
var innerAttrs = styles.style([ 'no-fill', 'no-events' ], {
stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke),
strokeWidth: 1.5
});
var expanded = isExpanded(element);
if (!expanded) {
attrs = {};
}
drawRect(
parentGfx,
getWidth(element, attrs),
getHeight(element, attrs),
TASK_BORDER_RADIUS - INNER_OUTER_DIST,
INNER_OUTER_DIST,
innerAttrs
);
return outer;
},
'bpmn:UserTask': function(parentGfx, element, attrs = {}) {
attrs = pickAttrs(attrs, [
'fill',
'stroke'
]);
var task = renderTask(parentGfx, element, attrs);
var x = 15;
var y = 12;
var pathDataUser1 = pathMap.getScaledPath('TASK_TYPE_USER_1', {
abspos: {
x: x,
y: y
}
});
drawPath(parentGfx, pathDataUser1, {
fill: getFillColor(element, defaultFillColor, attrs.fill),
stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke),
strokeWidth: 0.5
});
var pathDataUser2 = pathMap.getScaledPath('TASK_TYPE_USER_2', {
abspos: {
x: x,
y: y
}
});
drawPath(parentGfx, pathDataUser2, {
fill: getFillColor(element, defaultFillColor, attrs.fill),
stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke),
strokeWidth: 0.5
});
var pathDataUser3 = pathMap.getScaledPath('TASK_TYPE_USER_3', {
abspos: {
x: x,
y: y
}
});
drawPath(parentGfx, pathDataUser3, {
fill: getStrokeColor(element, defaultStrokeColor, attrs.stroke),
stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke),
strokeWidth: 0.5
});
return task;
},
'label': function(parentGfx, element, attrs = {}) {
return renderExternalLabel(parentGfx, element, attrs);
}
};
// extension API, use at your own risk
this._drawPath = drawPath;
this._renderer = renderer;
}
inherits(BpmnRenderer, BaseRenderer);
BpmnRenderer.$inject = [
'config.bpmnRenderer',
'eventBus',
'styles',
'pathMap',
'canvas',
'textRenderer'
];
/**
* @param {Element} element
*
* @return {boolean}
*/
BpmnRenderer.prototype.canRender = function(element) {
return is(element, 'bpmn:BaseElement');
};
/**
* Draw shape into parentGfx.
*
* @param {SVGElement} parentGfx
* @param {Shape} shape
* @param {Attrs} [attrs]
*
* @return {SVGElement} mainGfx
*/
BpmnRenderer.prototype.drawShape = function(parentGfx, shape, attrs = {}) {
var { type } = shape;
var handler = this._renderer(type);
return handler(parentGfx, shape, attrs);
};
/**
* Draw connection into parentGfx.
*
* @param {SVGElement} parentGfx
* @param {Connection} connection
* @param {Attrs} [attrs]
*
* @return {SVGElement} mainGfx
*/
BpmnRenderer.prototype.drawConnection = function(parentGfx, connection, attrs = {}) {
var { type } = connection;
var handler = this._renderer(type);
return handler(parentGfx, connection, attrs);
};
/**
* Get shape path.
*
* @param {Shape} shape
*
* @return {string} path
*/
BpmnRenderer.prototype.getShapePath = function(shape) {
if (isLabel(shape)) {
return getRoundRectPath(shape, EXTERNAL_LABEL_BORDER_RADIUS);
}
if (is(shape, 'bpmn:Event')) {
return getCirclePath(shape);
}
if (is(shape, 'bpmn:Activity')) {
return getRoundRectPath(shape, TASK_BORDER_RADIUS);
}
if (is(shape, 'bpmn:Gateway')) {
return getDiamondPath(shape);
}
return getRectPath(shape);
};
/**
* Pick attributes if they exist.
*
* @param {Object} attrs
* @param {string[]} keys
*
* @returns {Object}
*/
function pickAttrs(attrs, keys = []) {
return keys.reduce((pickedAttrs, key) => {
if (attrs[ key ]) {
pickedAttrs[ key ] = attrs[ key ];
}
return pickedAttrs;
}, {});
}
================================================
FILE: lib/draw/PathMap.js
================================================
/**
* Map containing SVG paths needed by BpmnRenderer
*/
export default function PathMap() {
/**
* Contains a map of path elements
*
* Path definition
* A parameterized path is defined like this:
*
* 'GATEWAY_PARALLEL': {
* d: 'm {mx},{my} {e.x0},0 0,{e.x1} {e.x1},0 0,{e.y0} -{e.x1},0 0,{e.y1} ' +
'-{e.x0},0 0,-{e.y1} -{e.x1},0 0,-{e.y0} {e.x1},0 z',
* height: 17.5,
* width: 17.5,
* heightElements: [2.5, 7.5],
* widthElements: [2.5, 7.5]
* }
*
* It's important to specify a correct height and width for the path as the scaling
* is based on the ratio between the specified height and width in this object and the
* height and width that is set as scale target (Note x,y coordinates will be scaled with
* individual ratios).
* The 'heightElements ' and 'widthElements ' array must contain the values that will be scaled.
* The scaling is based on the computed ratios.
* Coordinates on the y axis should be in the heightElement 's array, they will be scaled using
* the computed ratio coefficient.
* In the parameterized path the scaled values can be accessed through the 'e' object in {} brackets.
*
* The values for the y axis can be accessed in the path string using {e.y0}, {e.y1}, ....
* The values for the x axis can be accessed in the path string using {e.x0}, {e.x1}, ....
*
* The numbers x0, x1 respectively y0, y1, ... map to the corresponding array index.
*
*/
this.pathMap = {
'EVENT_MESSAGE': {
d: 'm {mx},{my} l 0,{e.y1} l {e.x1},0 l 0,-{e.y1} z l {e.x0},{e.y0} l {e.x0},-{e.y0}',
height: 36,
width: 36,
heightElements: [ 6, 14 ],
widthElements: [ 10.5, 21 ]
},
'EVENT_SIGNAL': {
d: 'M {mx},{my} l {e.x0},{e.y0} l -{e.x1},0 Z',
height: 36,
width: 36,
heightElements: [ 18 ],
widthElements: [ 10, 20 ]
},
'EVENT_ESCALATION': {
d: 'M {mx},{my} l {e.x0},{e.y0} l -{e.x0},-{e.y1} l -{e.x0},{e.y1} Z',
height: 36,
width: 36,
heightElements: [ 20, 7 ],
widthElements: [ 8 ]
},
'EVENT_CONDITIONAL': {
d: 'M {e.x0},{e.y0} l {e.x1},0 l 0,{e.y2} l -{e.x1},0 Z ' +
'M {e.x2},{e.y3} l {e.x0},0 ' +
'M {e.x2},{e.y4} l {e.x0},0 ' +
'M {e.x2},{e.y5} l {e.x0},0 ' +
'M {e.x2},{e.y6} l {e.x0},0 ' +
'M {e.x2},{e.y7} l {e.x0},0 ' +
'M {e.x2},{e.y8} l {e.x0},0 ',
height: 36,
width: 36,
heightElements: [ 8.5, 14.5, 18, 11.5, 14.5, 17.5, 20.5, 23.5, 26.5 ],
widthElements: [ 10.5, 14.5, 12.5 ]
},
'EVENT_LINK': {
d: 'm {mx},{my} 0,{e.y0} -{e.x1},0 0,{e.y1} {e.x1},0 0,{e.y0} {e.x0},-{e.y2} -{e.x0},-{e.y2} z',
height: 36,
width: 36,
heightElements: [ 4.4375, 6.75, 7.8125 ],
widthElements: [ 9.84375, 13.5 ]
},
'EVENT_ERROR': {
d: 'm {mx},{my} {e.x0},-{e.y0} {e.x1},-{e.y1} {e.x2},{e.y2} {e.x3},-{e.y3} -{e.x4},{e.y4} -{e.x5},-{e.y5} z',
height: 36,
width: 36,
heightElements: [ 0.023, 8.737, 8.151, 16.564, 10.591, 8.714 ],
widthElements: [ 0.085, 6.672, 6.97, 4.273, 5.337, 6.636 ]
},
'EVENT_CANCEL_45': {
d: 'm {mx},{my} -{e.x1},0 0,{e.x0} {e.x1},0 0,{e.y1} {e.x0},0 ' +
'0,-{e.y1} {e.x1},0 0,-{e.y0} -{e.x1},0 0,-{e.y1} -{e.x0},0 z',
height: 36,
width: 36,
heightElements: [ 4.75, 8.5 ],
widthElements: [ 4.75, 8.5 ]
},
'EVENT_COMPENSATION': {
d: 'm {mx},{my} {e.x0},-{e.y0} 0,{e.y1} z m {e.x1},-{e.y2} {e.x2},-{e.y3} 0,{e.y1} -{e.x2},-{e.y3} z',
height: 36,
width: 36,
heightElements: [ 6.5, 13, 0.4, 6.1 ],
widthElements: [ 9, 9.3, 8.7 ]
},
'EVENT_TIMER_WH': {
d: 'M {mx},{my} l {e.x0},-{e.y0} m -{e.x0},{e.y0} l {e.x1},{e.y1} ',
height: 36,
width: 36,
heightElements: [ 10, 2 ],
widthElements: [ 3, 7 ]
},
'EVENT_TIMER_LINE': {
d: 'M {mx},{my} ' +
'm {e.x0},{e.y0} l -{e.x1},{e.y1} ',
height: 36,
width: 36,
heightElements: [ 10, 3 ],
widthElements: [ 0, 0 ]
},
'EVENT_MULTIPLE': {
d:'m {mx},{my} {e.x1},-{e.y0} {e.x1},{e.y0} -{e.x0},{e.y1} -{e.x2},0 z',
height: 36,
width: 36,
heightElements: [ 6.28099, 12.56199 ],
widthElements: [ 3.1405, 9.42149, 12.56198 ]
},
'EVENT_PARALLEL_MULTIPLE': {
d:'m {mx},{my} {e.x0},0 0,{e.y1} {e.x1},0 0,{e.y0} -{e.x1},0 0,{e.y1} ' +
'-{e.x0},0 0,-{e.y1} -{e.x1},0 0,-{e.y0} {e.x1},0 z',
height: 36,
width: 36,
heightElements: [ 2.56228, 7.68683 ],
widthElements: [ 2.56228, 7.68683 ]
},
'GATEWAY_EXCLUSIVE': {
d:'m {mx},{my} {e.x0},{e.y0} {e.x1},{e.y0} {e.x2},0 {e.x4},{e.y2} ' +
'{e.x4},{e.y1} {e.x2},0 {e.x1},{e.y3} {e.x0},{e.y3} ' +
'{e.x3},0 {e.x5},{e.y1} {e.x5},{e.y2} {e.x3},0 z',
height: 17.5,
width: 17.5,
heightElements: [ 8.5, 6.5312, -6.5312, -8.5 ],
widthElements: [ 6.5, -6.5, 3, -3, 5, -5 ]
},
'GATEWAY_PARALLEL': {
d:'m {mx},{my} 0,{e.y1} -{e.x1},0 0,{e.y0} {e.x1},0 0,{e.y1} {e.x0},0 ' +
'0,-{e.y1} {e.x1},0 0,-{e.y0} -{e.x1},0 0,-{e.y1} -{e.x0},0 z',
height: 30,
width: 30,
heightElements: [ 5, 12.5 ],
widthElements: [ 5, 12.5 ]
},
'GATEWAY_EVENT_BASED': {
d:'m {mx},{my} {e.x0},{e.y0} {e.x0},{e.y1} {e.x1},{e.y2} {e.x2},0 z',
height: 11,
width: 11,
heightElements: [ -6, 6, 12, -12 ],
widthElements: [ 9, -3, -12 ]
},
'GATEWAY_COMPLEX': {
d:'m {mx},{my} 0,{e.y0} -{e.x0},-{e.y1} -{e.x1},{e.y2} {e.x0},{e.y1} -{e.x2},0 0,{e.y3} ' +
'{e.x2},0 -{e.x0},{e.y1} l {e.x1},{e.y2} {e.x0},-{e.y1} 0,{e.y0} {e.x3},0 0,-{e.y0} {e.x0},{e.y1} ' +
'{e.x1},-{e.y2} -{e.x0},-{e.y1} {e.x2},0 0,-{e.y3} -{e.x2},0 {e.x0},-{e.y1} -{e.x1},-{e.y2} ' +
'-{e.x0},{e.y1} 0,-{e.y0} -{e.x3},0 z',
height: 17.125,
width: 17.125,
heightElements: [ 4.875, 3.4375, 2.125, 3 ],
widthElements: [ 3.4375, 2.125, 4.875, 3 ]
},
'DATA_OBJECT_PATH': {
d:'m 0,0 {e.x1},0 {e.x0},{e.y0} 0,{e.y1} -{e.x2},0 0,-{e.y2} {e.x1},0 0,{e.y0} {e.x0},0',
height: 61,
width: 51,
heightElements: [ 10, 50, 60 ],
widthElements: [ 10, 40, 50, 60 ]
},
'DATA_OBJECT_COLLECTION_PATH': {
d: 'm{mx},{my} m 3,2 l 0,10 m 3,-10 l 0,10 m 3,-10 l 0,10',
height: 10,
width: 10,
heightElements: [],
widthElements: []
},
'DATA_ARROW': {
d:'m 5,9 9,0 0,-3 5,5 -5,5 0,-3 -9,0 z',
height: 61,
width: 51,
heightElements: [],
widthElements: []
},
'DATA_STORE': {
d:'m {mx},{my} ' +
'l 0,{e.y2} ' +
'c {e.x0},{e.y1} {e.x1},{e.y1} {e.x2},0 ' +
'l 0,-{e.y2} ' +
'c -{e.x0},-{e.y1} -{e.x1},-{e.y1} -{e.x2},0' +
'c {e.x0},{e.y1} {e.x1},{e.y1} {e.x2},0 ' +
'm -{e.x2},{e.y0}' +
'c {e.x0},{e.y1} {e.x1},{e.y1} {e.x2},0' +
'm -{e.x2},{e.y0}' +
'c {e.x0},{e.y1} {e.x1},{e.y1} {e.x2},0',
height: 61,
width: 61,
heightElements: [ 7, 10, 45 ],
widthElements: [ 2, 58, 60 ]
},
'TEXT_ANNOTATION': {
d: 'm {mx}, {my} m 10,0 l -10,0 l 0,{e.y0} l 10,0',
height: 30,
width: 10,
heightElements: [ 30 ],
widthElements: [ 10 ]
},
'MARKER_SUB_PROCESS': {
d: 'm{mx},{my} m 7,2 l 0,10 m -5,-5 l 10,0',
height: 10,
width: 10,
heightElements: [],
widthElements: []
},
'MARKER_PARALLEL': {
d: 'm{mx},{my} m 3,2 l 0,10 m 3,-10 l 0,10 m 3,-10 l 0,10',
height: 10,
width: 10,
heightElements: [],
widthElements: []
},
'MARKER_SEQUENTIAL': {
d: 'm{mx},{my} m 0,3 l 10,0 m -10,3 l 10,0 m -10,3 l 10,0',
height: 10,
width: 10,
heightElements: [],
widthElements: []
},
'MARKER_COMPENSATION': {
d: 'm {mx},{my} 7,-5 0,10 z m 7.1,-0.3 6.9,-4.7 0,10 -6.9,-4.7 z',
height: 10,
width: 21,
heightElements: [],
widthElements: []
},
'MARKER_LOOP': {
d: 'm {mx},{my} c 3.526979,0 6.386161,-2.829858 6.386161,-6.320661 0,-3.490806 -2.859182,-6.320661 ' +
'-6.386161,-6.320661 -3.526978,0 -6.38616,2.829855 -6.38616,6.320661 0,1.745402 ' +
'0.714797,3.325567 1.870463,4.469381 0.577834,0.571908 1.265885,1.034728 2.029916,1.35457 ' +
'l -0.718163,-3.909793 m 0.718163,3.909793 -3.885211,0.802902',
height: 13.9,
width: 13.7,
heightElements: [],
widthElements: []
},
'MARKER_ADHOC': {
d: 'm {mx},{my} m 0.84461,2.64411 c 1.05533,-1.23780996 2.64337,-2.07882 4.29653,-1.97997996 2.05163,0.0805 ' +
'3.85579,1.15803 5.76082,1.79107 1.06385,0.34139996 2.24454,0.1438 3.18759,-0.43767 0.61743,-0.33642 ' +
'1.2775,-0.64078 1.7542,-1.17511 0,0.56023 0,1.12046 0,1.6807 -0.98706,0.96237996 -2.29792,1.62393996 ' +
'-3.6918,1.66181996 -1.24459,0.0927 -2.46671,-0.2491 -3.59505,-0.74812 -1.35789,-0.55965 ' +
'-2.75133,-1.33436996 -4.27027,-1.18121996 -1.37741,0.14601 -2.41842,1.13685996 -3.44288,1.96782996 z',
height: 4,
width: 15,
heightElements: [],
widthElements: []
},
'TASK_TYPE_SEND': {
d: 'm {mx},{my} l 0,{e.y1} l {e.x1},0 l 0,-{e.y1} z l {e.x0},{e.y0} l {e.x0},-{e.y0}',
height: 14,
width: 21,
heightElements: [ 6, 14 ],
widthElements: [ 10.5, 21 ]
},
'TASK_TYPE_SCRIPT': {
d: 'm {mx},{my} c 9.966553,-6.27276 -8.000926,-7.91932 2.968968,-14.938 l -8.802728,0 ' +
'c -10.969894,7.01868 6.997585,8.66524 -2.968967,14.938 z ' +
'm -7,-12 l 5,0 ' +
'm -4.5,3 l 4.5,0 ' +
'm -3,3 l 5,0' +
'm -4,3 l 5,0',
height: 15,
width: 12.6,
heightElements: [ 6, 14 ],
widthElements: [ 10.5, 21 ]
},
'TASK_TYPE_USER_1': {
d: 'm {mx},{my} c 0.909,-0.845 1.594,-2.049 1.594,-3.385 0,-2.554 -1.805,-4.62199999 ' +
'-4.357,-4.62199999 -2.55199998,0 -4.28799998,2.06799999 -4.28799998,4.62199999 0,1.348 ' +
'0.974,2.562 1.89599998,3.405 -0.52899998,0.187 -5.669,2.097 -5.794,4.7560005 v 6.718 ' +
'h 17 v -6.718 c 0,-2.2980005 -5.5279996,-4.5950005 -6.0509996,-4.7760005 z' +
'm -8,6 l 0,5.5 m 11,0 l 0,-5'
},
'TASK_TYPE_USER_2': {
d: 'm {mx},{my} m 2.162,1.009 c 0,2.4470005 -2.158,4.4310005 -4.821,4.4310005 ' +
'-2.66499998,0 -4.822,-1.981 -4.822,-4.4310005 '
},
'TASK_TYPE_USER_3': {
d: 'm {mx},{my} m -6.9,-3.80 c 0,0 2.25099998,-2.358 4.27399998,-1.177 2.024,1.181 4.221,1.537 ' +
'4.124,0.965 -0.098,-0.57 -0.117,-3.79099999 -4.191,-4.13599999 -3.57499998,0.001 ' +
'-4.20799998,3.36699999 -4.20699998,4.34799999 z'
},
'TASK_TYPE_MANUAL': {
d: 'm {mx},{my} c 0.234,-0.01 5.604,0.008 8.029,0.004 0.808,0 1.271,-0.172 1.417,-0.752 0.227,-0.898 ' +
'-0.334,-1.314 -1.338,-1.316 -2.467,-0.01 -7.886,-0.004 -8.108,-0.004 -0.014,-0.079 0.016,-0.533 0,-0.61 ' +
'0.195,-0.042 8.507,0.006 9.616,0.002 0.877,-0.007 1.35,-0.438 1.353,-1.208 0.003,-0.768 -0.479,-1.09 ' +
'-1.35,-1.091 -2.968,-0.002 -9.619,-0.013 -9.619,-0.013 v -0.591 c 0,0 5.052,-0.016 7.225,-0.016 ' +
'0.888,-0.002 1.354,-0.416 1.351,-1.193 -0.006,-0.761 -0.492,-1.196 -1.361,-1.196 -3.473,-0.005 ' +
'-10.86,-0.003 -11.0829995,-0.003 -0.022,-0.047 -0.045,-0.094 -0.069,-0.139 0.3939995,-0.319 ' +
'2.0409995,-1.626 2.4149995,-2.017 0.469,-0.4870005 0.519,-1.1650005 0.162,-1.6040005 -0.414,-0.511 ' +
'-0.973,-0.5 -1.48,-0.236 -1.4609995,0.764 -6.5999995,3.6430005 -7.7329995,4.2710005 -0.9,0.499 ' +
'-1.516,1.253 -1.882,2.19 -0.37000002,0.95 -0.17,2.01 -0.166,2.979 0.004,0.718 -0.27300002,1.345 ' +
'-0.055,2.063 0.629,2.087 2.425,3.312 4.859,3.318 4.6179995,0.014 9.2379995,-0.139 13.8569995,-0.158 ' +
'0.755,-0.004 1.171,-0.301 1.182,-1.033 0.012,-0.754 -0.423,-0.969 -1.183,-0.973 -1.778,-0.01 ' +
'-5.824,-0.004 -6.04,-0.004 10e-4,-0.084 0.003,-0.586 10e-4,-0.67 z'
},
'TASK_TYPE_INSTANTIATING_SEND': {
d: 'm {mx},{my} l 0,8.4 l 12.6,0 l 0,-8.4 z l 6.3,3.6 l 6.3,-3.6'
},
'TASK_TYPE_SERVICE': {
d: 'm {mx},{my} v -1.71335 c 0.352326,-0.0705 0.703932,-0.17838 1.047628,-0.32133 ' +
'0.344416,-0.14465 0.665822,-0.32133 0.966377,-0.52145 l 1.19431,1.18005 1.567487,-1.57688 ' +
'-1.195028,-1.18014 c 0.403376,-0.61394 0.683079,-1.29908 0.825447,-2.01824 l 1.622133,-0.01 ' +
'v -2.2196 l -1.636514,0.01 c -0.07333,-0.35153 -0.178319,-0.70024 -0.323564,-1.04372 ' +
'-0.145244,-0.34406 -0.321407,-0.6644 -0.522735,-0.96217 l 1.131035,-1.13631 -1.583305,-1.56293 ' +
'-1.129598,1.13589 c -0.614052,-0.40108 -1.302883,-0.68093 -2.022633,-0.82247 l 0.0093,-1.61852 ' +
'h -2.241173 l 0.0042,1.63124 c -0.353763,0.0736 -0.705369,0.17977 -1.049785,0.32371 -0.344415,0.14437 ' +
'-0.665102,0.32092 -0.9635006,0.52046 l -1.1698628,-1.15823 -1.5667691,1.5792 1.1684265,1.15669 ' +
'c -0.4026573,0.61283 -0.68308,1.29797 -0.8247287,2.01713 l -1.6588041,0.003 v 2.22174 ' +
'l 1.6724648,-0.006 c 0.073327,0.35077 0.1797598,0.70243 0.3242851,1.04472 0.1452428,0.34448 ' +
'0.3214064,0.6644 0.5227339,0.96066 l -1.1993431,1.19723 1.5840256,1.56011 1.1964668,-1.19348 ' +
'c 0.6140517,0.40346 1.3028827,0.68232 2.0233517,0.82331 l 7.19e-4,1.69892 h 2.226848 z ' +
'm 0.221462,-3.9957 c -1.788948,0.7502 -3.8576,-0.0928 -4.6097055,-1.87438 -0.7521065,-1.78321 ' +
'0.090598,-3.84627 1.8802645,-4.59604 1.78823,-0.74936 3.856881,0.0929 4.608987,1.87437 ' +
'0.752106,1.78165 -0.0906,3.84612 -1.879546,4.59605 z'
},
'TASK_TYPE_SERVICE_FILL': {
d: 'm {mx},{my} c -1.788948,0.7502 -3.8576,-0.0928 -4.6097055,-1.87438 -0.7521065,-1.78321 ' +
'0.090598,-3.84627 1.8802645,-4.59604 1.78823,-0.74936 3.856881,0.0929 4.608987,1.87437 ' +
'0.752106,1.78165 -0.0906,3.84612 -1.879546,4.59605 z'
},
'TASK_TYPE_BUSINESS_RULE_HEADER': {
d: 'm {mx},{my} 0,4 20,0 0,-4 z'
},
'TASK_TYPE_BUSINESS_RULE_MAIN': {
d: 'm {mx},{my} 0,12 20,0 0,-12 z' +
'm 0,8 l 20,0 ' +
'm -13,-4 l 0,8'
},
'MESSAGE_FLOW_MARKER': {
d: 'm {mx},{my} m -10.5 ,-7 l 0,14 l 21,0 l 0,-14 z l 10.5,6 l 10.5,-6'
}
};
/**
* Return raw path for the given ID.
*
* @param {string} pathId
*
* @return {string} raw path
*/
this.getRawPath = function getRawPath(pathId) {
return this.pathMap[pathId].d;
};
/**
* Scales the path to the given height and width.
* Use case
* Use case is to scale the content of elements (event, gateways) based
* on the element bounding box's size.
*
* Why not transform
* Scaling a path with transform() will also scale the stroke and IE does not support
* the option 'non-scaling-stroke' to prevent this.
* Also there are use cases where only some parts of a path should be
* scaled.
*
* @param {string} pathId The ID of the path.
* @param {Object} param
* Example param object scales the path to 60% size of the container (data.width, data.height).
*
* {
* xScaleFactor: 0.6,
* yScaleFactor:0.6,
* containerWidth: data.width,
* containerHeight: data.height,
* position: {
* mx: 0.46,
* my: 0.2,
* }
* }
*
*
* targetpathwidth = xScaleFactor * containerWidth
* targetpathheight = yScaleFactor * containerHeight
* Position is used to set the starting coordinate of the path. M is computed:
*
* position.x * containerWidth
* position.y * containerHeight
*
* Center of the container position: {
* mx: 0.5,
* my: 0.5,
* }
* Upper left corner of the container
* position: {
* mx: 0.0,
* my: 0.0,
* }
*
*
*
*
* @return {string} scaled path
*/
this.getScaledPath = function getScaledPath(pathId, param) {
var rawPath = this.pathMap[pathId];
// positioning
// compute the start point of the path
var mx, my;
if (param.abspos) {
mx = param.abspos.x;
my = param.abspos.y;
} else {
mx = param.containerWidth * param.position.mx;
my = param.containerHeight * param.position.my;
}
var coordinates = {}; // map for the scaled coordinates
if (param.position) {
// path
var heightRatio = (param.containerHeight / rawPath.height) * param.yScaleFactor;
var widthRatio = (param.containerWidth / rawPath.width) * param.xScaleFactor;
// Apply height ratio
for (var heightIndex = 0; heightIndex < rawPath.heightElements.length; heightIndex++) {
coordinates['y' + heightIndex] = rawPath.heightElements[heightIndex] * heightRatio;
}
// Apply width ratio
for (var widthIndex = 0; widthIndex < rawPath.widthElements.length; widthIndex++) {
coordinates['x' + widthIndex] = rawPath.widthElements[widthIndex] * widthRatio;
}
}
// Apply value to raw path
var path = format(
rawPath.d, {
mx: mx,
my: my,
e: coordinates
}
);
return path;
};
}
// helpers //////////////////////
// copied and adjusted from https://github.com/adobe-webplatform/Snap.svg/blob/master/src/svg.js
var tokenRegex = /\{([^{}]+)\}/g,
objNotationRegex = /(?:(?:^|\.)(.+?)(?=\[|\.|$|\()|\[('|")(.+?)\2\])(\(\))?/g; // matches .xxxxx or ["xxxxx"] to run over object properties
function replacer(all, key, obj) {
var res = obj;
key.replace(objNotationRegex, function(all, name, quote, quotedName, isFunc) {
name = name || quotedName;
if (res) {
if (name in res) {
res = res[name];
}
typeof res == 'function' && isFunc && (res = res());
}
});
res = (res == null || res == obj ? all : res) + '';
return res;
}
function format(str, obj) {
return String(str).replace(tokenRegex, function(all, key) {
return replacer(all, key, obj);
});
}
================================================
FILE: lib/draw/TextRenderer.js
================================================
import { assign } from 'min-dash';
import TextUtil from 'diagram-js/lib/util/Text';
var DEFAULT_FONT_SIZE = 12;
var LINE_HEIGHT_RATIO = 1.2;
var MIN_TEXT_ANNOTATION_HEIGHT = 30;
/**
* @typedef { {
* fontFamily: string;
* fontSize: number;
* fontWeight: string;
* lineHeight: number;
* } } TextRendererStyle
*
* @typedef { {
* defaultStyle?: Partial;
* externalStyle?: Partial;
* } } TextRendererConfig
*
* @typedef { import('diagram-js/lib/util/Text').TextLayoutConfig } TextLayoutConfig
*
* @typedef { import('diagram-js/lib/util/Types').Rect } Rect
*/
/**
* Renders text and computes text bounding boxes.
*
* @param {TextRendererConfig} [config]
*/
export default function TextRenderer(config) {
var defaultStyle = assign({
fontFamily: 'Arial, sans-serif',
fontSize: DEFAULT_FONT_SIZE,
fontWeight: 'normal',
lineHeight: LINE_HEIGHT_RATIO
}, config && config.defaultStyle || {});
var fontSize = parseInt(defaultStyle.fontSize, 10) - 1;
var externalStyle = assign({}, defaultStyle, {
fontSize: fontSize
}, config && config.externalStyle || {});
var textUtil = new TextUtil({
style: defaultStyle
});
/**
* Get the new bounds of an externally rendered,
* layouted label.
*
* @param {Rect} bounds
* @param {string} text
*
* @return {Rect}
*/
this.getExternalLabelBounds = function(bounds, text) {
var layoutedDimensions = textUtil.getDimensions(text, {
box: {
width: 90,
height: 30
},
style: externalStyle
});
// resize label shape to fit label text
return {
x: Math.round(bounds.x + bounds.width / 2 - layoutedDimensions.width / 2),
y: Math.round(bounds.y),
width: Math.ceil(layoutedDimensions.width),
height: Math.ceil(layoutedDimensions.height)
};
};
/**
* Get the new bounds of text annotation.
*
* @param {Rect} bounds
* @param {string} text
*
* @return {Rect}
*/
this.getTextAnnotationBounds = function(bounds, text) {
var layoutedDimensions = textUtil.getDimensions(text, {
box: bounds,
style: defaultStyle,
align: 'left-top',
padding: 5
});
return {
x: bounds.x,
y: bounds.y,
width: bounds.width,
height: Math.max(MIN_TEXT_ANNOTATION_HEIGHT, Math.round(layoutedDimensions.height))
};
};
/**
* Create a layouted text element.
*
* @param {string} text
* @param {TextLayoutConfig} [options]
*
* @return {SVGElement} rendered text
*/
this.createText = function(text, options) {
return textUtil.createText(text, options || {});
};
/**
* Get default text style.
*/
this.getDefaultStyle = function() {
return defaultStyle;
};
/**
* Get the external text style.
*/
this.getExternalStyle = function() {
return externalStyle;
};
}
TextRenderer.$inject = [
'config.textRenderer'
];
================================================
FILE: lib/draw/TextRenderer.spec.ts
================================================
import TextRenderer from './TextRenderer';
new TextRenderer({
defaultStyle: {
fontFamily: 'foo'
}
});
const textRenderer = new TextRenderer();
const externalLabelBounds = textRenderer.getExternalLabelBounds({
x: 100,
y: 100,
width: 100,
height: 100
}, 'FOO\nBAR\n\BAZ');
const textAnnotationBounds = textRenderer.getTextAnnotationBounds({
x: 100,
y: 100,
width: 100,
height: 100
}, 'FOO\nBAR\n\BAZ');
let text = textRenderer.createText('foo');
text = textRenderer.createText('foo', {
align: 'center-top',
padding: 10
});
const defaultStyle = textRenderer.getDefaultStyle();
const externalStyle = textRenderer.getExternalStyle();
================================================
FILE: lib/draw/index.js
================================================
import BpmnRenderer from './BpmnRenderer';
import TextRenderer from './TextRenderer';
import PathMap from './PathMap';
export default {
__init__: [ 'bpmnRenderer' ],
bpmnRenderer: [ 'type', BpmnRenderer ],
textRenderer: [ 'type', TextRenderer ],
pathMap: [ 'type', PathMap ]
};
================================================
FILE: lib/features/align-elements/AlignElementsContextPadProvider.js
================================================
import {
assign
} from 'min-dash';
import ICONS from './AlignElementsIcons';
/**
* @typedef {import('diagram-js/lib/core/Canvas').default} Canvas
* @typedef {import('diagram-js/lib/features/context-pad/ContextPad').default} ContextPad
* @typedef {import('diagram-js/lib/features/popup-menu/PopupMenu').default} PopupMenu
* @typedef {import('diagram-js/lib/i18n/translate/translate').default} Translate
*
* @typedef {import('../../model/Types').Element} Element
* @typedef {import('diagram-js/lib/features/context-pad/ContextPad').ContextPadEntries} ContextPadEntries
* @typedef {import('diagram-js/lib/features/context-pad/ContextPadProvider').default} ContextPadProvider
*/
var LOW_PRIORITY = 900;
/**
* A provider for the `Align elements` context pad entry.
*
* @implements {ContextPadProvider}
*
* @param {ContextPad} contextPad
* @param {PopupMenu} popupMenu
* @param {Translate} translate
* @param {Canvas} canvas
*/
export default function AlignElementsContextPadProvider(contextPad, popupMenu, translate, canvas) {
contextPad.registerProvider(LOW_PRIORITY, this);
this._contextPad = contextPad;
this._popupMenu = popupMenu;
this._translate = translate;
this._canvas = canvas;
}
AlignElementsContextPadProvider.$inject = [
'contextPad',
'popupMenu',
'translate',
'canvas'
];
/**
* @param {Element[]} elements
*
* @return {ContextPadEntries}
*/
AlignElementsContextPadProvider.prototype.getMultiElementContextPadEntries = function(elements) {
var actions = {};
if (this._isAllowed(elements)) {
assign(actions, this._getEntries(elements));
}
return actions;
};
AlignElementsContextPadProvider.prototype._isAllowed = function(elements) {
return !this._popupMenu.isEmpty(elements, 'align-elements');
};
AlignElementsContextPadProvider.prototype._getEntries = function() {
var self = this;
return {
'align-elements': {
group: 'align-elements',
title: self._translate('Align elements'),
html: `${ICONS['align']}
`,
action: {
click: function(event, target) {
var position = self._getMenuPosition(target);
assign(position, {
cursor: {
x: event.x,
y: event.y
}
});
self._popupMenu.open(target, 'align-elements', position);
}
}
}
};
};
AlignElementsContextPadProvider.prototype._getMenuPosition = function(elements) {
var Y_OFFSET = 5;
var pad = this._contextPad.getPad(elements).html;
var padRect = pad.getBoundingClientRect();
var pos = {
x: padRect.left,
y: padRect.bottom + Y_OFFSET
};
return pos;
};
================================================
FILE: lib/features/align-elements/AlignElementsIcons.js
================================================
/**
* To change the icons, modify the SVGs in `./resources`, execute `npx svgo -f resources --datauri enc -o dist`,
* and then replace respective icons with the optimized data URIs in `./dist`.
*/
var icons = {
align: `
`,
bottom: `
`,
center: `
`,
left: `
`,
right: `
`,
top: `
`,
middle: `
`
};
export default icons;
================================================
FILE: lib/features/align-elements/AlignElementsMenuProvider.js
================================================
import ICONS from './AlignElementsIcons';
import {
assign,
forEach,
} from 'min-dash';
/**
* @typedef {import('diagram-js/lib/features/align-elements/AlignElements').default} AlignElements
* @typedef {import('diagram-js/lib/features/popup-menu/PopupMenu').default} PopupMenu
* @typedef {import('diagram-js/lib/features/rules/Rules').default} Rules
* @typedef {import('diagram-js/lib/i18n/translate/translate').default} Translate
*
* @typedef {import('diagram-js/lib/features/popup-menu/PopupMenu').PopupMenuEntries} PopupMenuEntries
* @typedef {import('diagram-js/lib/features/popup-menu/PopupMenuProvider').default} PopupMenuProvider
* @typedef {import('diagram-js/lib/features/popup-menu/PopupMenu').PopupMenuTarget} PopupMenuTarget
*/
var ALIGNMENT_OPTIONS = [
'left',
'center',
'right',
'top',
'middle',
'bottom'
];
/**
* A provider for the `Align elements` popup menu.
*
* @implements {PopupMenuProvider}
*
* @param {PopupMenu} popupMenu
* @param {AlignElements} alignElements
* @param {Translate} translate
* @param {Rules} rules
*/
export default function AlignElementsMenuProvider(popupMenu, alignElements, translate, rules) {
this._alignElements = alignElements;
this._translate = translate;
this._popupMenu = popupMenu;
this._rules = rules;
popupMenu.registerProvider('align-elements', this);
}
AlignElementsMenuProvider.$inject = [
'popupMenu',
'alignElements',
'translate',
'rules'
];
/**
* @param {PopupMenuTarget} target
*
* @return {PopupMenuEntries}
*/
AlignElementsMenuProvider.prototype.getPopupMenuEntries = function(target) {
var entries = {};
if (this._isAllowed(target)) {
assign(entries, this._getEntries(target));
}
return entries;
};
AlignElementsMenuProvider.prototype._isAllowed = function(target) {
return this._rules.allowed('elements.align', { elements: target });
};
/**
* @param {PopupMenuTarget} target
*
* @return {PopupMenuEntries}
*/
AlignElementsMenuProvider.prototype._getEntries = function(target) {
var alignElements = this._alignElements,
translate = this._translate,
popupMenu = this._popupMenu;
var entries = {};
forEach(ALIGNMENT_OPTIONS, function(alignment) {
entries[ 'align-elements-' + alignment ] = {
group: 'align',
title: translate('Align elements ' + alignment),
className: 'bjs-align-elements-menu-entry',
imageHtml: ICONS[ alignment ],
action: function() {
alignElements.trigger(target, alignment);
popupMenu.close();
}
};
});
return entries;
};
================================================
FILE: lib/features/align-elements/BpmnAlignElements.js
================================================
import inherits from 'inherits-browser';
import RuleProvider from 'diagram-js/lib/features/rules/RuleProvider';
import { getParents } from 'diagram-js/lib/util/Elements';
import {
filter
} from 'min-dash';
/**
* @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
*/
/**
* Rule provider for aligning BPMN elements.
*
* @param {EventBus} eventBus
*/
export default function BpmnAlignElements(eventBus) {
RuleProvider.call(this, eventBus);
}
BpmnAlignElements.$inject = [ 'eventBus' ];
inherits(BpmnAlignElements, RuleProvider);
BpmnAlignElements.prototype.init = function() {
this.addRule('elements.align', function(context) {
var elements = context.elements;
// filter out elements which cannot be aligned
var filteredElements = filter(elements, function(element) {
return !(element.waypoints || element.host || element.labelTarget);
});
// filter out elements which are children of any of the selected elements
filteredElements = getParents(filteredElements);
if (filteredElements.length < 2) {
return false;
}
return filteredElements;
});
};
================================================
FILE: lib/features/align-elements/index.js
================================================
import AlignElementsModule from 'diagram-js/lib/features/align-elements';
import ContextPadModule from 'diagram-js/lib/features/context-pad';
import PopupMenuModule from 'diagram-js/lib/features/popup-menu';
import AlignElementsContextPadProvider from './AlignElementsContextPadProvider';
import AlignElementsMenuProvider from './AlignElementsMenuProvider';
import BpmnAlignElements from './BpmnAlignElements';
export default {
__depends__: [
AlignElementsModule,
ContextPadModule,
PopupMenuModule
],
__init__: [
'alignElementsContextPadProvider',
'alignElementsMenuProvider',
'bpmnAlignElements'
],
alignElementsContextPadProvider: [ 'type', AlignElementsContextPadProvider ],
alignElementsMenuProvider: [ 'type', AlignElementsMenuProvider ],
bpmnAlignElements: [ 'type', BpmnAlignElements ]
};
================================================
FILE: lib/features/append-preview/AppendPreview.js
================================================
import {
assign,
isNil
} from 'min-dash';
const round = Math.round;
/**
* @typedef {import('diagram-js/lib/features/complex-preview/ComplexPreview').default} ComplexPreview
* @typedef {import('diagram-js/lib/layout/ConnectionDocking').default} ConnectionDocking
* @typedef {import('../modeling/ElementFactory').default} ElementFactory
* @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
* @typedef {import('../modeling/BpmnLayouter').default} BpmnLayouter
* @typedef {import('diagram-js/lib/features/rules/Rules').default} Rules
*
* @typedef {import('../../model/Types').Shape} Shape
*/
/**
* A preview for appending.
*
* @param {ComplexPreview} complexPreview
* @param {ConnectionDocking} connectionDocking
* @param {ElementFactory} elementFactory
* @param {EventBus} eventBus
* @param {BpmnLayouter} layouter
* @param {Rules} rules
*/
export default function AppendPreview(complexPreview, connectionDocking, elementFactory, eventBus, layouter, rules) {
this._complexPreview = complexPreview;
this._connectionDocking = connectionDocking;
this._elementFactory = elementFactory;
this._eventBus = eventBus;
this._layouter = layouter;
this._rules = rules;
}
/**
* Create a preview of appending a shape of the given type to the given source.
*
* @param {Shape} source
* @param {string} type
* @param {Partial} options
*/
AppendPreview.prototype.create = function(source, type, options) {
const complexPreview = this._complexPreview,
connectionDocking = this._connectionDocking,
elementFactory = this._elementFactory,
eventBus = this._eventBus,
layouter = this._layouter,
rules = this._rules;
const shape = elementFactory.createShape(assign({ type }, options));
const position = eventBus.fire('autoPlace', {
source,
shape
});
if (!position) {
return;
}
assign(shape, {
x: position.x - round(shape.width / 2),
y: position.y - round(shape.height / 2)
});
const connectionCreateAllowed = rules.allowed('connection.create', {
source,
target: shape,
hints: {
targetParent: source.parent
}
});
let connection = null;
if (connectionCreateAllowed) {
connection = elementFactory.createConnection(connectionCreateAllowed);
connection.waypoints = layouter.layoutConnection(connection, {
source,
target: shape
});
connection.waypoints = connectionDocking.getCroppedWaypoints(connection, source, shape);
}
complexPreview.create({
created: [
shape,
connection
].filter((element) => !isNil(element))
});
};
AppendPreview.prototype.cleanUp = function() {
this._complexPreview.cleanUp();
};
AppendPreview.$inject = [
'complexPreview',
'connectionDocking',
'elementFactory',
'eventBus',
'layouter',
'rules'
];
================================================
FILE: lib/features/append-preview/index.js
================================================
import AutoPlaceModule from '../auto-place';
import ComplexPreviewModule from 'diagram-js/lib/features/complex-preview';
import ModelingModule from '../modeling';
import AppendPreview from './AppendPreview';
export default {
__depends__: [
AutoPlaceModule,
ComplexPreviewModule,
ModelingModule
],
__init__: [ 'appendPreview' ],
appendPreview: [ 'type', AppendPreview ]
};
================================================
FILE: lib/features/auto-place/BpmnAutoPlace.js
================================================
import { getNewShapePosition } from './BpmnAutoPlaceUtil';
/**
* @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
* @typedef {import('diagram-js/lib/core/ElementRegistry').default} ElementRegistry
*/
/**
* BPMN auto-place behavior.
*
* @param {EventBus} eventBus
* @param {ElementRegistry} elementRegistry
*/
export default function AutoPlace(eventBus, elementRegistry) {
eventBus.on('autoPlace', function(context) {
var shape = context.shape,
source = context.source;
return getNewShapePosition(source, shape, elementRegistry);
});
}
AutoPlace.$inject = [ 'eventBus', 'elementRegistry' ];
================================================
FILE: lib/features/auto-place/BpmnAutoPlaceUtil.js
================================================
import { is } from '../../util/ModelUtil';
import {
isAny,
isDirectionHorizontal
} from '../modeling/util/ModelingUtil';
import {
getMid,
asTRBL,
getOrientation
} from 'diagram-js/lib/layout/LayoutUtil';
import {
findFreePosition,
generateGetNextPosition,
getConnectedDistance
} from 'diagram-js/lib/features/auto-place/AutoPlaceUtil';
import { isConnection } from 'diagram-js/lib/util/ModelUtil';
/**
* @typedef {import('../../model/Types').Shape} Shape
*
* @typedef {import('diagram-js/lib/core/ElementRegistry').default} ElementRegistry
* @typedef {import('diagram-js/lib/util/Types').Point} Point
* @typedef {import('diagram-js/lib/util/Types').DirectionTRBL} DirectionTRBL
*/
/**
* Get the position for given new target relative to the source it will be
* connected to.
*
* @param {Shape} source
* @param {Shape} element
* @param {ElementRegistry} elementRegistry
*
* @return {Point}
*/
export function getNewShapePosition(source, element, elementRegistry) {
var placeHorizontally = isDirectionHorizontal(source, elementRegistry);
if (is(element, 'bpmn:TextAnnotation')) {
return getTextAnnotationPosition(source, element, placeHorizontally);
}
if (isAny(element, [ 'bpmn:DataObjectReference', 'bpmn:DataStoreReference' ])) {
return getDataElementPosition(source, element, placeHorizontally);
}
if (is(element, 'bpmn:FlowNode')) {
return getFlowNodePosition(source, element, placeHorizontally);
}
}
/**
* Get the position for given new flow node. Try placing the flow node right/bottom of
* the source.
*
* @param {Shape} source
* @param {Shape} element
* @param {boolean} placeHorizontally Whether to place the new element horizontally
*
* @return {Point}
*/
export function getFlowNodePosition(source, element, placeHorizontally) {
var sourceTrbl = asTRBL(source);
var sourceMid = getMid(source);
var placement = placeHorizontally ? {
directionHint: 'e',
minDistance: 80,
baseOrientation: 'left',
boundaryOrientation: 'top',
start: 'top',
end: 'bottom'
} : {
directionHint: 's',
minDistance: 90,
baseOrientation: 'top',
boundaryOrientation: 'left',
start: 'left',
end: 'right'
};
var connectedDistance = getConnectedDistance(source, {
filter: function(connection) {
return is(connection, 'bpmn:SequenceFlow');
},
direction: placement.directionHint
});
var margin = 30,
minDistance = placement.minDistance,
orientation = placement.baseOrientation;
if (is(source, 'bpmn:BoundaryEvent')) {
orientation = getOrientation(source, source.host, -25);
if (orientation.indexOf(placement.boundaryOrientation) !== -1) {
margin *= -1;
}
}
var position = placeHorizontally ? {
x: sourceTrbl.right + connectedDistance + element.width / 2,
y: sourceMid.y + getDistance(orientation, minDistance, placement)
} : {
x: sourceMid.x + getDistance(orientation, minDistance, placement),
y: sourceTrbl.bottom + connectedDistance + element.height / 2
};
var nextPosition = {
margin: margin,
minDistance: minDistance
};
var nextPositionDirection = placeHorizontally ? {
y: nextPosition
} : {
x: nextPosition
};
return findFreePosition(source, element, position, generateGetNextPosition(nextPositionDirection));
}
/**
* @param {DirectionTRBL} orientation
* @param {number} minDistance
* @param {{ start: DirectionTRBL, end: DirectionTRBL }} placement
*
* @return {number}
*/
function getDistance(orientation, minDistance, placement) {
if (orientation.includes(placement.start)) {
return -1 * minDistance;
} else if (orientation.includes(placement.end)) {
return minDistance;
} else {
return 0;
}
}
/**
* Get the position for given text annotation. Try placing the text annotation
* top-right of the source (bottom-right in vertical layouts).
*
* @param {Shape} source
* @param {Shape} element
* @param {boolean} placeHorizontally Whether to place the new element horizontally
*
* @return {Point}
*/
export function getTextAnnotationPosition(source, element, placeHorizontally) {
var sourceTrbl = asTRBL(source);
var position = placeHorizontally ? {
x: sourceTrbl.right + element.width / 2,
y: sourceTrbl.top - 50 - element.height / 2
} : {
x: sourceTrbl.right + 50 + element.width / 2,
y: sourceTrbl.bottom + element.height / 2
};
if (isConnection(source)) {
position = getMid(source);
if (placeHorizontally) {
position.x += 100;
position.y -= 50;
} else {
position.x += 100;
position.y += 50;
}
}
var nextPosition = {
margin: placeHorizontally ? -30 : 30,
minDistance: 20
};
var nextPositionDirection = placeHorizontally ? {
y: nextPosition
} : {
x: nextPosition
};
return findFreePosition(source, element, position, generateGetNextPosition(nextPositionDirection));
}
/**
* Get the position for given new data element. Try placing the data element
* bottom-right of the source (bottom-left in vertical layouts).
*
* @param {Shape} source
* @param {Shape} element
* @param {boolean} placeHorizontally Whether to place the new element horizontally
*
* @return {Point}
*/
export function getDataElementPosition(source, element, placeHorizontally) {
var sourceTrbl = asTRBL(source);
var position = placeHorizontally ? {
x: sourceTrbl.right - 10 + element.width / 2,
y: sourceTrbl.bottom + 40 + element.width / 2
} : {
x: sourceTrbl.left - 40 - element.width / 2,
y: sourceTrbl.bottom - 10 + element.height / 2
};
var nextPosition = {
margin: 30,
minDistance: 30
};
var nextPositionDirection = placeHorizontally ? {
x: nextPosition
} : {
y: nextPosition
};
return findFreePosition(source, element, position, generateGetNextPosition(nextPositionDirection));
}
================================================
FILE: lib/features/auto-place/index.js
================================================
import AutoPlaceModule from 'diagram-js/lib/features/auto-place';
import BpmnAutoPlace from './BpmnAutoPlace';
export default {
__depends__: [ AutoPlaceModule ],
__init__: [ 'bpmnAutoPlace' ],
bpmnAutoPlace: [ 'type', BpmnAutoPlace ]
};
================================================
FILE: lib/features/auto-resize/BpmnAutoResize.js
================================================
import AutoResize from 'diagram-js/lib/features/auto-resize/AutoResize';
import inherits from 'inherits-browser';
import { is } from '../../util/ModelUtil';
/**
* @typedef {import('didi').Injector} Injector
*
* @typedef {import('../../model/Types').Shape} Shape
*
* @typedef {import('diagram-js/lib/util/Types').Rect} Rect
*/
/**
* BPMN-specific resize behavior.
*
* @param {Injector} injector
*/
export default function BpmnAutoResize(injector) {
injector.invoke(AutoResize, this);
}
BpmnAutoResize.$inject = [
'injector'
];
inherits(BpmnAutoResize, AutoResize);
/**
* Perform BPMN-specific resizing of participants.
*
* @param {Shape} target
* @param {Rect} newBounds
* @param {Object} [hints]
* @param {string} [hints.autoResize]
*/
BpmnAutoResize.prototype.resize = function(target, newBounds, hints) {
if (is(target, 'bpmn:Participant')) {
this._modeling.resizeLane(target, newBounds, null, hints);
} else {
this._modeling.resizeShape(target, newBounds, null, hints);
}
};
================================================
FILE: lib/features/auto-resize/BpmnAutoResizeProvider.js
================================================
import { is } from '../../util/ModelUtil';
import { isLabel } from '../../util/LabelUtil';
import inherits from 'inherits-browser';
import { forEach } from 'min-dash';
import AutoResizeProvider from 'diagram-js/lib/features/auto-resize/AutoResizeProvider';
/**
* @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
* @typedef {import('../modeling/Modeling').default} Modeling
*
* @typedef {import('../../model/Types').Shape} Shape
*/
/**
* BPMN-specific provider for automatic resizung.
*
* @param {EventBus} eventBus
* @param {Modeling} modeling
*/
export default function BpmnAutoResizeProvider(eventBus, modeling) {
AutoResizeProvider.call(this, eventBus);
this._modeling = modeling;
}
inherits(BpmnAutoResizeProvider, AutoResizeProvider);
BpmnAutoResizeProvider.$inject = [
'eventBus',
'modeling'
];
/**
* BPMN-specific check whether given elements can be resized.
*
* @param {Shape[]} elements
* @param {Shape} target
*
* @return {boolean}
*/
BpmnAutoResizeProvider.prototype.canResize = function(elements, target) {
// do not resize plane elements:
// root elements, collapsed sub-processes
if (is(target.di, 'bpmndi:BPMNPlane')) {
return false;
}
if (!is(target, 'bpmn:Participant') && !is(target, 'bpmn:Lane') && !(is(target, 'bpmn:SubProcess'))) {
return false;
}
var canResize = true;
forEach(elements, function(element) {
if (is(element, 'bpmn:Lane') || isLabel(element)) {
canResize = false;
return;
}
});
return canResize;
};
================================================
FILE: lib/features/auto-resize/index.js
================================================
import BpmnAutoResize from './BpmnAutoResize';
import BpmnAutoResizeProvider from './BpmnAutoResizeProvider';
export default {
__init__: [
'bpmnAutoResize',
'bpmnAutoResizeProvider'
],
bpmnAutoResize: [ 'type', BpmnAutoResize ],
bpmnAutoResizeProvider: [ 'type', BpmnAutoResizeProvider ]
};
================================================
FILE: lib/features/context-pad/ContextPadProvider.js
================================================
import {
assign,
forEach,
isArray,
every
} from 'min-dash';
import {
is
} from '../../util/ModelUtil';
import {
isExpanded,
isHorizontal,
isEventSubProcess
} from '../../util/DiUtil';
import {
isAny
} from '../modeling/util/ModelingUtil';
import {
getChildLanes
} from '../modeling/util/LaneUtil';
import {
hasPrimaryModifier
} from 'diagram-js/lib/util/Mouse';
/**
* @typedef {import('didi').Injector} Injector
* @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
* @typedef {import('diagram-js/lib/features/context-pad/ContextPad').default} ContextPad
* @typedef {import('../modeling/Modeling').default} Modeling
* @typedef {import('../modeling/ElementFactory').default} ElementFactory
* @typedef {import('../append-preview/AppendPreview').default} AppendPreview
* @typedef {import('diagram-js/lib/features/connect/Connect').default} Connect
* @typedef {import('diagram-js/lib/features/create/Create').default} Create
* @typedef {import('diagram-js/lib/features/popup-menu/PopupMenu').default} PopupMenu
* @typedef {import('diagram-js/lib/features/canvas/Canvas').default} Canvas
* @typedef {import('diagram-js/lib/features/rules/Rules').default} Rules
* @typedef {import('diagram-js/lib/i18n/translate/translate').default} Translate
*
* @typedef {import('../../model/Types').Element} Element
* @typedef {import('../../model/Types').ModdleElement} ModdleElement
*
* @typedef {import('diagram-js/lib/features/context-pad/ContextPadProvider').default} BaseContextPadProvider
* @typedef {import('diagram-js/lib/features/context-pad/ContextPadProvider').ContextPadEntries} ContextPadEntries
* @typedef {import('diagram-js/lib/features/context-pad/ContextPadProvider').ContextPadEntry} ContextPadEntry
*
* @typedef { { autoPlace?: boolean; } } ContextPadConfig
*/
/**
* BPMN-specific context pad provider.
*
* @implements {BaseContextPadProvider}
*
* @param {ContextPadConfig} config
* @param {Injector} injector
* @param {EventBus} eventBus
* @param {ContextPad} contextPad
* @param {Modeling} modeling
* @param {ElementFactory} elementFactory
* @param {Connect} connect
* @param {Create} create
* @param {PopupMenu} popupMenu
* @param {Canvas} canvas
* @param {Rules} rules
* @param {Translate} translate
* @param {AppendPreview} appendPreview
*/
export default function ContextPadProvider(
config, injector, eventBus,
contextPad, modeling, elementFactory,
connect, create, popupMenu,
canvas, rules, translate, appendPreview) {
config = config || {};
contextPad.registerProvider(this);
this._contextPad = contextPad;
this._modeling = modeling;
this._elementFactory = elementFactory;
this._connect = connect;
this._create = create;
this._popupMenu = popupMenu;
this._canvas = canvas;
this._rules = rules;
this._translate = translate;
this._eventBus = eventBus;
this._appendPreview = appendPreview;
if (config.autoPlace !== false) {
this._autoPlace = injector.get('autoPlace', false);
}
eventBus.on('create.end', 250, function(event) {
var context = event.context,
shape = context.shape;
if (!hasPrimaryModifier(event) || !contextPad.isOpen(shape)) {
return;
}
var entries = contextPad.getEntries(shape);
if (entries.replace) {
entries.replace.action.click(event, shape);
}
});
eventBus.on('contextPad.close', function() {
appendPreview.cleanUp();
});
}
ContextPadProvider.$inject = [
'config.contextPad',
'injector',
'eventBus',
'contextPad',
'modeling',
'elementFactory',
'connect',
'create',
'popupMenu',
'canvas',
'rules',
'translate',
'appendPreview'
];
/**
* @param {Element[]} elements
*
* @return {ContextPadEntries}
*/
ContextPadProvider.prototype.getMultiElementContextPadEntries = function(elements) {
var modeling = this._modeling;
var actions = {};
if (this._isDeleteAllowed(elements)) {
assign(actions, {
'delete': {
group: 'edit',
className: 'bpmn-icon-trash',
title: this._translate('Delete'),
action: {
click: function(event, elements) {
modeling.removeElements(elements.slice());
}
}
}
});
}
return actions;
};
/**
* @param {Element[]} elements
*
* @return {boolean}
*/
ContextPadProvider.prototype._isDeleteAllowed = function(elements) {
var baseAllowed = this._rules.allowed('elements.delete', {
elements: elements
});
if (isArray(baseAllowed)) {
return every(elements, el => baseAllowed.includes(el));
}
return baseAllowed;
};
/**
* @param {Element} element
*
* @return {ContextPadEntries}
*/
ContextPadProvider.prototype.getContextPadEntries = function(element) {
var contextPad = this._contextPad,
modeling = this._modeling,
elementFactory = this._elementFactory,
connect = this._connect,
create = this._create,
popupMenu = this._popupMenu,
autoPlace = this._autoPlace,
translate = this._translate,
appendPreview = this._appendPreview;
var actions = {};
if (element.type === 'label') {
if (this._isDeleteAllowed([ element ])) {
assign(actions, deleteAction());
}
return actions;
}
var businessObject = element.businessObject;
function startConnect(event, element) {
connect.start(event, element);
}
function removeElement(e, element) {
modeling.removeElements([ element ]);
}
function deleteAction() {
return {
'delete': {
group: 'edit',
className: 'bpmn-icon-trash',
title: translate('Delete'),
action: {
click: removeElement
}
}
};
}
function getReplaceMenuPosition(element) {
var Y_OFFSET = 5;
var pad = contextPad.getPad(element).html;
var padRect = pad.getBoundingClientRect();
var pos = {
x: padRect.left,
y: padRect.bottom + Y_OFFSET
};
return pos;
}
/**
* Create an append action.
*
* @param {string} type
* @param {string} className
* @param {string} title
* @param {Object} [options]
*
* @return {ContextPadEntry}
*/
function appendAction(type, className, title, options) {
function appendStart(event, element) {
var shape = elementFactory.createShape(assign({ type: type }, options));
create.start(event, shape, {
source: element
});
}
var append = autoPlace ? function(_, element) {
var shape = elementFactory.createShape(assign({ type: type }, options));
autoPlace.append(element, shape);
} : appendStart;
var previewAppend = autoPlace ? function(_, element) {
// mouseover
appendPreview.create(element, type, options);
return () => {
// mouseout
appendPreview.cleanUp();
};
} : null;
return {
group: 'model',
className: className,
title: title,
action: {
dragstart: appendStart,
click: append,
hover: previewAppend
}
};
}
function splitLaneHandler(count) {
return function(_, element) {
// actual split
modeling.splitLane(element, count);
// refresh context pad after split to
// get rid of split icons
contextPad.open(element, true);
};
}
if (isAny(businessObject, [ 'bpmn:Lane', 'bpmn:Participant' ]) && isExpanded(element)) {
var childLanes = getChildLanes(element);
assign(actions, {
'lane-insert-above': {
group: 'lane-insert-above',
className: 'bpmn-icon-lane-insert-above',
title: translate('Add lane above'),
action: {
click: function(event, element) {
modeling.addLane(element, 'top');
}
}
}
});
if (childLanes.length < 2) {
if (isHorizontal(element) ? element.height >= 120 : element.width >= 120) {
assign(actions, {
'lane-divide-two': {
group: 'lane-divide',
className: 'bpmn-icon-lane-divide-two',
title: translate('Divide into two lanes'),
action: {
click: splitLaneHandler(2)
}
}
});
}
if (isHorizontal(element) ? element.height >= 180 : element.width >= 180) {
assign(actions, {
'lane-divide-three': {
group: 'lane-divide',
className: 'bpmn-icon-lane-divide-three',
title: translate('Divide into three lanes'),
action: {
click: splitLaneHandler(3)
}
}
});
}
}
assign(actions, {
'lane-insert-below': {
group: 'lane-insert-below',
className: 'bpmn-icon-lane-insert-below',
title: translate('Add lane below'),
action: {
click: function(event, element) {
modeling.addLane(element, 'bottom');
}
}
}
});
}
if (is(businessObject, 'bpmn:FlowNode')) {
if (is(businessObject, 'bpmn:EventBasedGateway')) {
assign(actions, {
'append.receive-task': appendAction(
'bpmn:ReceiveTask',
'bpmn-icon-receive-task',
translate('Append receive task')
),
'append.message-intermediate-event': appendAction(
'bpmn:IntermediateCatchEvent',
'bpmn-icon-intermediate-event-catch-message',
translate('Append message intermediate catch event'),
{ eventDefinitionType: 'bpmn:MessageEventDefinition' }
),
'append.timer-intermediate-event': appendAction(
'bpmn:IntermediateCatchEvent',
'bpmn-icon-intermediate-event-catch-timer',
translate('Append timer intermediate catch event'),
{ eventDefinitionType: 'bpmn:TimerEventDefinition' }
),
'append.condition-intermediate-event': appendAction(
'bpmn:IntermediateCatchEvent',
'bpmn-icon-intermediate-event-catch-condition',
translate('Append conditional intermediate catch event'),
{ eventDefinitionType: 'bpmn:ConditionalEventDefinition' }
),
'append.signal-intermediate-event': appendAction(
'bpmn:IntermediateCatchEvent',
'bpmn-icon-intermediate-event-catch-signal',
translate('Append signal intermediate catch event'),
{ eventDefinitionType: 'bpmn:SignalEventDefinition' }
)
});
} else if (isEventType(businessObject, 'bpmn:BoundaryEvent', 'bpmn:CompensateEventDefinition')) {
assign(actions, {
'append.compensation-activity':
appendAction(
'bpmn:Task',
'bpmn-icon-task',
translate('Append compensation activity'),
{
isForCompensation: true
}
)
});
} else if (!is(businessObject, 'bpmn:EndEvent') &&
!businessObject.isForCompensation &&
!isEventType(businessObject, 'bpmn:IntermediateThrowEvent', 'bpmn:LinkEventDefinition') &&
!isEventSubProcess(businessObject)) {
assign(actions, {
'append.end-event': appendAction(
'bpmn:EndEvent',
'bpmn-icon-end-event-none',
translate('Append end event')
),
'append.gateway': appendAction(
'bpmn:ExclusiveGateway',
'bpmn-icon-gateway-none',
translate('Append gateway')
),
'append.append-task': appendAction(
'bpmn:Task',
'bpmn-icon-task',
translate('Append task')
),
'append.intermediate-event': appendAction(
'bpmn:IntermediateThrowEvent',
'bpmn-icon-intermediate-event-none',
translate('Append intermediate/boundary event')
)
});
}
}
if (!popupMenu.isEmpty(element, 'bpmn-replace')) {
// Replace menu entry
assign(actions, {
'replace': {
group: 'edit',
className: 'bpmn-icon-screw-wrench',
title: translate('Change element'),
action: {
click: function(event, element) {
var position = assign(getReplaceMenuPosition(element), {
cursor: { x: event.x, y: event.y }
});
popupMenu.open(element, 'bpmn-replace', position, {
title: translate('Change element'),
width: 300,
search: true
});
}
}
}
});
}
if (is(businessObject, 'bpmn:SequenceFlow')) {
assign(actions, {
'append.text-annotation': appendAction(
'bpmn:TextAnnotation',
'bpmn-icon-text-annotation',
translate('Add text annotation')
)
});
}
if (is(businessObject, 'bpmn:MessageFlow')) {
assign(actions, {
'append.text-annotation': appendAction(
'bpmn:TextAnnotation',
'bpmn-icon-text-annotation',
translate('Add text annotation')
)
});
}
if (
isAny(businessObject, [
'bpmn:FlowNode',
'bpmn:InteractionNode',
'bpmn:DataObjectReference',
'bpmn:DataStoreReference',
])
) {
assign(actions, {
'append.text-annotation': appendAction(
'bpmn:TextAnnotation',
'bpmn-icon-text-annotation',
translate('Add text annotation')
),
'connect': {
group: 'connect',
className: 'bpmn-icon-connection-multi',
title: translate('Connect to other element'),
action: {
click: startConnect,
dragstart: startConnect,
},
},
});
}
if (is(businessObject, 'bpmn:TextAnnotation')) {
assign(actions, {
'connect': {
group: 'connect',
className: 'bpmn-icon-connection-multi',
title: translate('Connect using association'),
action: {
click: startConnect,
dragstart: startConnect,
},
},
});
}
if (isAny(businessObject, [ 'bpmn:DataObjectReference', 'bpmn:DataStoreReference' ])) {
assign(actions, {
'connect': {
group: 'connect',
className: 'bpmn-icon-connection-multi',
title: translate('Connect using data input association'),
action: {
click: startConnect,
dragstart: startConnect
}
}
});
}
if (is(businessObject, 'bpmn:Group')) {
assign(actions, {
'append.text-annotation': appendAction(
'bpmn:TextAnnotation',
'bpmn-icon-text-annotation',
translate('Add text annotation')
)
});
}
if (this._isDeleteAllowed([ element ])) {
assign(actions, deleteAction());
}
return actions;
};
// helpers /////////
/**
* @param {ModdleElement} businessObject
* @param {string} type
* @param {string} eventDefinitionType
*
* @return {boolean}
*/
function isEventType(businessObject, type, eventDefinitionType) {
var isType = businessObject.$instanceOf(type);
var isDefinition = false;
var definitions = businessObject.eventDefinitions || [];
forEach(definitions, function(def) {
if (def.$type === eventDefinitionType) {
isDefinition = true;
}
});
return isType && isDefinition;
}
================================================
FILE: lib/features/context-pad/index.js
================================================
import AppendPreviewModule from '../append-preview';
import DirectEditingModule from 'diagram-js-direct-editing';
import ContextPadModule from 'diagram-js/lib/features/context-pad';
import SelectionModule from 'diagram-js/lib/features/selection';
import ConnectModule from 'diagram-js/lib/features/connect';
import CreateModule from 'diagram-js/lib/features/create';
import PopupMenuModule from '../popup-menu';
import ContextPadProvider from './ContextPadProvider';
export default {
__depends__: [
AppendPreviewModule,
DirectEditingModule,
ContextPadModule,
SelectionModule,
ConnectModule,
CreateModule,
PopupMenuModule
],
__init__: [ 'contextPadProvider' ],
contextPadProvider: [ 'type', ContextPadProvider ]
};
================================================
FILE: lib/features/copy-paste/BpmnCopyPaste.js
================================================
import {
getBusinessObject,
getDi,
is
} from '../../util/ModelUtil';
import { collectElementsAnnotations } from '../../util/AnnotationUtil';
import {
forEach,
isArray,
isUndefined,
omit,
reduce
} from 'min-dash';
import { isLabel } from '../../util/LabelUtil';
/**
* @typedef {import('../modeling/BpmnFactory').default} BpmnFactory
* @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
* @typedef {import('./ModdleCopy').default} ModdleCopy
*/
function copyProperties(source, target, properties) {
if (!isArray(properties)) {
properties = [ properties ];
}
forEach(properties, function(property) {
if (!isUndefined(source[property])) {
target[property] = source[property];
}
});
}
var LOW_PRIORITY = 750;
/**
* BPMN-specific copy & paste.
*
* @param {BpmnFactory} bpmnFactory
* @param {EventBus} eventBus
* @param {ModdleCopy} moddleCopy
*/
export default function BpmnCopyPaste(bpmnFactory, eventBus, moddleCopy) {
function copy(bo, clone) {
var targetBo = bpmnFactory.create(bo.$type);
return moddleCopy.copyElement(bo, targetBo, null, clone);
}
eventBus.on('copyPaste.copyElement', LOW_PRIORITY, function(context) {
var descriptor = context.descriptor,
element = context.element,
businessObject = getBusinessObject(element);
// do not copy business object + di for labels;
// will be pulled from the referenced label target
if (isLabel(element)) {
return descriptor;
}
var businessObjectCopy = descriptor.businessObject = copy(businessObject, true);
var diCopy = descriptor.di = copy(getDi(element), true);
diCopy.bpmnElement = businessObjectCopy;
copyProperties(businessObjectCopy, descriptor, 'name');
copyProperties(diCopy, descriptor, 'isExpanded');
// default sequence flow
if (businessObject.default) {
descriptor.default = businessObject.default.id;
}
});
var referencesKey = '-bpmn-js-refs';
function getReferences(cache) {
return (cache[referencesKey] = cache[referencesKey] || {});
}
function setReferences(cache, references) {
cache[referencesKey] = references;
}
function resolveReferences(descriptor, cache, references) {
var businessObject = getBusinessObject(descriptor);
// default sequence flows
if (descriptor.default) {
// relationship cannot be resolved immediately
references[ descriptor.default ] = {
element: businessObject,
property: 'default'
};
}
// boundary events
if (descriptor.host) {
// relationship can be resolved immediately
getBusinessObject(descriptor).attachedToRef = getBusinessObject(cache[ descriptor.host ]);
}
return omit(references, reduce(references, function(array, reference, key) {
var element = reference.element,
property = reference.property;
if (key === descriptor.id) {
element.set(property, businessObject);
array.push(descriptor.id);
}
return array;
}, []));
}
eventBus.on('copyPaste.pasteElement', function(context) {
var cache = context.cache,
descriptor = context.descriptor,
businessObject = descriptor.businessObject,
di = descriptor.di;
// wire existing di + businessObject for external label
if (isLabel(descriptor)) {
descriptor.businessObject = getBusinessObject(cache[ descriptor.labelTarget ]);
descriptor.di = getDi(cache[ descriptor.labelTarget ]);
return;
}
businessObject = descriptor.businessObject = copy(businessObject);
di = descriptor.di = copy(di);
di.bpmnElement = businessObject;
copyProperties(descriptor, businessObject, [
'isExpanded',
'name'
]);
descriptor.type = businessObject.$type;
});
// copy + paste processRef with participant
eventBus.on('copyPaste.copyElement', LOW_PRIORITY, function(context) {
var descriptor = context.descriptor,
element = context.element;
if (!is(element, 'bpmn:Participant')) {
return;
}
var participantBo = getBusinessObject(element);
if (participantBo.processRef) {
descriptor.processRef = copy(participantBo.processRef, true);
}
});
eventBus.on('copyPaste.pasteElement', function(context) {
var descriptor = context.descriptor,
processRef = descriptor.processRef;
if (processRef) {
descriptor.processRef = copy(processRef);
}
});
eventBus.on('copyPaste.createTree', function(context) {
var element = context.element,
children = context.children;
if (!is(element, 'bpmn:SubProcess')) {
return;
}
// add TextAnnotations to copy the closure,
// since by default they are children of a global process, not subprocess
forEach(collectElementsAnnotations(children), (entry) => {
children.push(entry.annotation);
});
});
// resolve references
eventBus.on('copyPaste.pasteElement', LOW_PRIORITY, function(context) {
var cache = context.cache,
descriptor = context.descriptor;
// resolve references e.g. default sequence flow
setReferences(
cache,
resolveReferences(descriptor, cache, getReferences(cache))
);
});
}
BpmnCopyPaste.$inject = [
'bpmnFactory',
'eventBus',
'moddleCopy'
];
================================================
FILE: lib/features/copy-paste/ModdleCopy.js
================================================
import {
find,
forEach,
has,
isArray,
isDefined,
isObject,
matchPattern,
reduce,
sortBy
} from 'min-dash';
import { is } from '../../util/ModelUtil';
const DISALLOWED_PROPERTIES = [
'artifacts',
'dataInputAssociations',
'dataOutputAssociations',
'default',
'flowElements',
'lanes',
'incoming',
'outgoing',
'categoryValue'
];
const ALLOWED_REFERENCES = [
'errorRef',
'escalationRef',
'messageRef',
'signalRef',
'dataObjectRef'
];
/**
* @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
* @typedef {import('../modeling/BpmnFactory').default} BpmnFactory
* @typedef {import('../../model/Types').Moddle} Moddle
*
* @typedef {import('../../model/Types').ModdleElement} ModdleElement
*/
/**
* Utility for copying model properties from source element to target element.
*
* @param {EventBus} eventBus
* @param {BpmnFactory} bpmnFactory
* @param {Moddle} moddle
*/
export default function ModdleCopy(eventBus, bpmnFactory, moddle) {
this._bpmnFactory = bpmnFactory;
this._eventBus = eventBus;
this._moddle = moddle;
// copy extension elements last
eventBus.on('moddleCopy.canCopyProperties', (context) => {
const { propertyNames } = context;
if (!propertyNames || !propertyNames.length) {
return;
}
return sortBy(propertyNames, (propertyName) => {
return propertyName === 'extensionElements';
});
});
// default check whether property can be copied
eventBus.on('moddleCopy.canCopyProperty', (context) => {
const {
parent,
property,
propertyName
} = context;
const parentDescriptor = isObject(parent) && parent.$descriptor;
if (propertyName && ALLOWED_REFERENCES.includes(propertyName)) {
// allow copying reference
return property;
}
if (propertyName && DISALLOWED_PROPERTIES.includes(propertyName)) {
// disallow copying property
return false;
}
if (propertyName &&
parentDescriptor &&
!find(parentDescriptor.properties, matchPattern({ name: propertyName }))) {
// disallow copying property
return false;
}
});
// do NOT allow to copy empty extension elements
eventBus.on('moddleCopy.canSetCopiedProperty', (context) => {
const { property } = context;
if (is(property, 'bpmn:ExtensionElements') && (!property.values || !property.values.length)) {
// disallow setting copied property
return false;
}
});
}
ModdleCopy.$inject = [
'eventBus',
'bpmnFactory',
'moddle'
];
/**
* Copy model properties of source element to target element.
*
* @param {ModdleElement} sourceElement
* @param {ModdleElement} targetElement
* @param {string[]} [propertyNames]
* @param {boolean} [clone=false]
*
* @return {ModdleElement}
*/
ModdleCopy.prototype.copyElement = function(sourceElement, targetElement, propertyNames, clone = false) {
if (propertyNames && !isArray(propertyNames)) {
propertyNames = [ propertyNames ];
}
propertyNames = propertyNames || getPropertyNames(sourceElement.$descriptor);
const canCopyProperties = this._eventBus.fire('moddleCopy.canCopyProperties', {
propertyNames: propertyNames,
sourceElement: sourceElement,
targetElement: targetElement,
clone: clone
});
if (canCopyProperties === false) {
return targetElement;
}
if (isArray(canCopyProperties)) {
propertyNames = canCopyProperties;
}
// copy properties
forEach(propertyNames, (propertyName) => {
let sourceProperty;
if (has(sourceElement, propertyName)) {
sourceProperty = sourceElement.get(propertyName);
}
const copiedProperty = this.copyProperty(sourceProperty, targetElement, propertyName, clone);
if (!isDefined(copiedProperty)) {
return;
}
const canSetProperty = this._eventBus.fire('moddleCopy.canSetCopiedProperty', {
parent: targetElement,
property: copiedProperty,
propertyName: propertyName
});
if (canSetProperty === false) {
return;
}
// TODO(nikku): unclaim old IDs if ID property is copied over
// this._moddle.getPropertyDescriptor(parent, propertyName)
targetElement.set(propertyName, copiedProperty);
});
return targetElement;
};
/**
* Copy model property.
*
* @param {any} property
* @param {ModdleElement} parent
* @param {string} propertyName
* @param {boolean} [clone=false]
*
* @return {any}
*/
ModdleCopy.prototype.copyProperty = function(property, parent, propertyName, clone = false) {
// allow others to copy property
let copiedProperty = this._eventBus.fire('moddleCopy.canCopyProperty', {
parent: parent,
property: property,
propertyName: propertyName,
clone: clone
});
// return if copying is NOT allowed
if (copiedProperty === false) {
return;
}
if (copiedProperty) {
if (isObject(copiedProperty) && copiedProperty.$type && !copiedProperty.$parent) {
copiedProperty.$parent = parent;
}
return copiedProperty;
}
const propertyDescriptor = this._moddle.getPropertyDescriptor(parent, propertyName);
// do NOT copy references
if (propertyDescriptor.isReference) {
return;
}
// copy id
if (propertyDescriptor.isId) {
return property && this._copyId(property, parent, clone);
}
// copy arrays
if (isArray(property)) {
return reduce(property, (childProperties, childProperty) => {
// recursion
const copiedProperty = this.copyProperty(childProperty, parent, propertyName, clone);
// copying might NOT be allowed
if (copiedProperty) {
return childProperties.concat(copiedProperty);
}
return childProperties;
}, []);
}
// copy model elements
if (isObject(property) && property.$type) {
if (this._moddle.getElementDescriptor(property).isGeneric) {
return;
}
copiedProperty = this._bpmnFactory.create(property.$type);
copiedProperty.$parent = parent;
// recursion
copiedProperty = this.copyElement(property, copiedProperty, null, clone);
return copiedProperty;
}
// copy primitive properties
return property;
};
ModdleCopy.prototype._copyId = function(id, element, clone = false) {
if (clone) {
return id;
}
// disallow if already taken
if (this._moddle.ids.assigned(id)) {
return;
} else {
this._moddle.ids.claim(id, element);
return id;
}
};
// helpers //////////
export function getPropertyNames(descriptor, keepDefaultProperties) {
return reduce(descriptor.properties, (properties, property) => {
if (keepDefaultProperties && property.default) {
return properties;
}
return properties.concat(property.name);
}, []);
}
================================================
FILE: lib/features/copy-paste/index.js
================================================
import CopyPasteModule from 'diagram-js/lib/features/copy-paste';
import BpmnCopyPaste from './BpmnCopyPaste';
import ModdleCopy from './ModdleCopy';
export default {
__depends__: [
CopyPasteModule
],
__init__: [ 'bpmnCopyPaste', 'moddleCopy' ],
bpmnCopyPaste: [ 'type', BpmnCopyPaste ],
moddleCopy: [ 'type', ModdleCopy ]
};
================================================
FILE: lib/features/di-ordering/BpmnDiOrdering.js
================================================
import { getDi } from '../../util/ModelUtil';
import {
filter,
forEach,
map
} from 'min-dash';
import { selfAndAllChildren } from 'diagram-js/lib/util/Elements';
/**
* @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
* @typedef {import('diagram-js/lib/core/Canvas').default} Canvas
*/
var HIGH_PRIORITY = 2000;
/**
* @param {EventBus} eventBus
* @param {Canvas} canvas
*/
export default function BpmnDiOrdering(eventBus, canvas) {
eventBus.on('saveXML.start', HIGH_PRIORITY, orderDi);
function orderDi() {
var rootElements = canvas.getRootElements();
forEach(rootElements, function(root) {
var rootDi = getDi(root),
elements,
diElements;
elements = selfAndAllChildren([ root ], false);
// only bpmndi:Shape and bpmndi:Edge can be direct children of bpmndi:Plane
elements = filter(elements, function(element) {
return element !== root && !element.labelTarget;
});
diElements = map(elements, getDi);
rootDi.set('planeElement', diElements);
});
}
}
BpmnDiOrdering.$inject = [ 'eventBus', 'canvas' ];
================================================
FILE: lib/features/di-ordering/index.js
================================================
import BpmnDiOrdering from '../di-ordering/BpmnDiOrdering';
export default {
__init__: [
'bpmnDiOrdering'
],
bpmnDiOrdering: [ 'type', BpmnDiOrdering ]
};
================================================
FILE: lib/features/distribute-elements/BpmnDistributeElements.js
================================================
import inherits from 'inherits-browser';
import RuleProvider from 'diagram-js/lib/features/rules/RuleProvider';
import { getParents } from 'diagram-js/lib/util/Elements';
import {
filter
} from 'min-dash';
import {
isAny
} from '../modeling/util/ModelingUtil';
/**
* @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
*/
/**
* Registers element exclude filters for elements that currently do not support
* distribution.
*
* @param {EventBus} eventBus
*/
export default function BpmnDistributeElements(eventBus) {
RuleProvider.call(this, eventBus);
}
BpmnDistributeElements.$inject = [ 'eventBus' ];
inherits(BpmnDistributeElements, RuleProvider);
BpmnDistributeElements.prototype.init = function() {
this.addRule('elements.distribute', function(context) {
var elements = context.elements;
elements = filter(elements, function(element) {
var cannotDistribute = isAny(element, [
'bpmn:Association',
'bpmn:BoundaryEvent',
'bpmn:DataInputAssociation',
'bpmn:DataOutputAssociation',
'bpmn:Lane',
'bpmn:MessageFlow',
'bpmn:SequenceFlow',
'bpmn:TextAnnotation'
]);
return !(element.labelTarget || cannotDistribute);
});
// filter out elements which are children of any of the selected elements
elements = getParents(elements);
if (elements.length < 3) {
return false;
}
return elements;
});
};
================================================
FILE: lib/features/distribute-elements/DistributeElementsIcons.js
================================================
/**
* To change the icons, modify the SVGs in `./resources`, execute `npx svgo -f resources --datauri enc -o dist`,
* and then replace respective icons with the optimized data URIs in `./dist`.
*/
var icons = {
horizontal: `
`,
vertical: `
`
};
export default icons;
================================================
FILE: lib/features/distribute-elements/DistributeElementsMenuProvider.js
================================================
import ICONS from './DistributeElementsIcons';
import { assign } from 'min-dash';
/**
* @typedef {import('diagram-js/lib/features/popup-menu/PopupMenu').default} PopupMenu
* @typedef {import('./BpmnDistributeElements').default} DistributeElements
* @typedef {import('diagram-js/lib/i18n/translate/translate').default} Translate
* @typedef {import('diagram-js/lib/features/rules/Rules').default} Rules
*
* @typedef {import('diagram-js/lib/features/popup-menu/PopupMenuProvider').PopupMenuEntries} PopupMenuEntries
* @typedef {import('diagram-js/lib/features/popup-menu/PopupMenuProvider').default} PopupMenuProvider
* @typedef {import('diagram-js/lib/features/popup-menu/PopupMenu').PopupMenuTarget} PopupMenuTarget
*/
var LOW_PRIORITY = 900;
/**
* A provider for the distribute elements popup menu.
*
* @implements {PopupMenuProvider}
*
* @param {PopupMenu} popupMenu
* @param {DistributeElements} distributeElements
* @param {Translate} translate
* @param {Rules} rules
*/
export default function DistributeElementsMenuProvider(
popupMenu, distributeElements, translate, rules) {
this._distributeElements = distributeElements;
this._translate = translate;
this._popupMenu = popupMenu;
this._rules = rules;
popupMenu.registerProvider('align-elements', LOW_PRIORITY, this);
}
DistributeElementsMenuProvider.$inject = [
'popupMenu',
'distributeElements',
'translate',
'rules'
];
/**
* @param {PopupMenuTarget} target
*
* @return {PopupMenuEntries}
*/
DistributeElementsMenuProvider.prototype.getPopupMenuEntries = function(target) {
var entries = {};
if (this._isAllowed(target)) {
assign(entries, this._getEntries(target));
}
return entries;
};
DistributeElementsMenuProvider.prototype._isAllowed = function(elements) {
return this._rules.allowed('elements.distribute', { elements: elements });
};
DistributeElementsMenuProvider.prototype._getEntries = function(elements) {
var distributeElements = this._distributeElements,
translate = this._translate,
popupMenu = this._popupMenu;
var entries = {
'distribute-elements-horizontal': {
group: 'distribute',
title: translate('Distribute elements horizontally'),
className: 'bjs-align-elements-menu-entry',
imageHtml: ICONS['horizontal'],
action: function(event, entry) {
distributeElements.trigger(elements, 'horizontal');
popupMenu.close();
}
},
'distribute-elements-vertical': {
group: 'distribute',
title: translate('Distribute elements vertically'),
imageHtml: ICONS['vertical'],
action: function(event, entry) {
distributeElements.trigger(elements, 'vertical');
popupMenu.close();
}
},
};
return entries;
};
================================================
FILE: lib/features/distribute-elements/index.js
================================================
import DistributeElementsModule from 'diagram-js/lib/features/distribute-elements';
import PopupMenuModule from 'diagram-js/lib/features/popup-menu';
import BpmnDistributeElements from './BpmnDistributeElements';
import DistributeElementsMenuProvider from './DistributeElementsMenuProvider';
export default {
__depends__: [
PopupMenuModule,
DistributeElementsModule
],
__init__: [
'bpmnDistributeElements',
'distributeElementsMenuProvider'
],
bpmnDistributeElements: [ 'type', BpmnDistributeElements ],
distributeElementsMenuProvider: [ 'type', DistributeElementsMenuProvider ]
};
================================================
FILE: lib/features/drilldown/DrilldownBreadcrumbs.js
================================================
import { domify, classes } from 'min-dom';
import { find } from 'min-dash';
import { escapeHTML } from 'diagram-js/lib/util/EscapeUtil';
import { getBusinessObject, is } from '../../util/ModelUtil';
import {
getPlaneIdFromShape
} from '../../util/DrilldownUtil';
/**
* @typedef {import('diagram-js/lib/core/Canvas').default} Canvas
* @typedef {import('diagram-js/lib/core/ElementRegistry').default} ElementRegistry
* @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
*
* @typedef {import('../../model/Types').Element} Element
* @typedef {import('../../model/Types').Shape} Shape
*/
var OPEN_CLASS = 'bjs-breadcrumbs-shown';
/**
* Adds overlays that allow switching planes on collapsed subprocesses.
*
* @param {EventBus} eventBus
* @param {ElementRegistry} elementRegistry
* @param {Canvas} canvas
*/
export default function DrilldownBreadcrumbs(eventBus, elementRegistry, canvas) {
var breadcrumbs = domify('');
var container = canvas.getContainer();
var containerClasses = classes(container);
container.appendChild(breadcrumbs);
var businessObjectParents = [];
// update breadcrumbs if name or ID of the primary shape changes
eventBus.on('element.changed', function(event) {
var shape = event.element,
businessObject = getBusinessObject(shape);
var isPresent = find(businessObjectParents, function(element) {
return element === businessObject;
});
if (!isPresent) {
return;
}
updateBreadcrumbs();
});
/**
* Updates the displayed breadcrumbs. If no element is provided, only the
* labels are updated.
*
* @param {Element} [element]
*/
function updateBreadcrumbs(element) {
if (element) {
businessObjectParents = getBusinessObjectParentChain(element);
}
var path = businessObjectParents.flatMap(function(parent) {
var parentPlane =
canvas.findRoot(getPlaneIdFromShape(parent)) ||
canvas.findRoot(parent.id);
// when the root is a collaboration, the process does not have a
// corresponding element in the elementRegisty. Instead, we search
// for the corresponding participant
if (!parentPlane && is(parent, 'bpmn:Process')) {
var participant = elementRegistry.find(function(element) {
var businessObject = getBusinessObject(element);
return businessObject && businessObject.get('processRef') === parent;
});
parentPlane = participant && canvas.findRoot(participant.id);
}
if (!parentPlane) {
return [];
}
var title = escapeHTML(parent.name || parent.id);
var link = domify('' + title + ' ');
link.addEventListener('click', function() {
canvas.setRootElement(parentPlane);
});
return link;
});
breadcrumbs.innerHTML = '';
// show breadcrumbs and expose state to .djs-container
var visible = path.length > 1;
containerClasses.toggle(OPEN_CLASS, visible);
path.forEach(function(element) {
breadcrumbs.appendChild(element);
});
}
eventBus.on('root.set', function(event) {
updateBreadcrumbs(event.element);
});
}
DrilldownBreadcrumbs.$inject = [ 'eventBus', 'elementRegistry', 'canvas' ];
// helpers //////////
/**
* Returns the parents for the element using the business object chain,
* starting with the root element.
*
* @param {Shape} child
*
* @return {Shape}
*/
function getBusinessObjectParentChain(child) {
var businessObject = getBusinessObject(child);
var parents = [];
for (var element = businessObject; element; element = element.$parent) {
if (is(element, 'bpmn:SubProcess') || is(element, 'bpmn:Process')) {
parents.push(element);
}
}
return parents.reverse();
}
================================================
FILE: lib/features/drilldown/DrilldownCentering.js
================================================
import { is } from '../../util/ModelUtil';
/**
* @typedef {import('diagram-js/lib/core/Canvas').default} Canvas
* @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
*/
/**
* Move collapsed subprocesses into view when drilling down.
*
* Zoom and scroll are saved in a session.
*
* @param {EventBus} eventBus
* @param {Canvas} canvas
*/
export default function DrilldownCentering(eventBus, canvas) {
var currentRoot = null;
var positionMap = new Map();
eventBus.on('root.set', function(event) {
var newRoot = event.element;
var currentViewbox = canvas.viewbox();
var storedViewbox = positionMap.get(newRoot);
positionMap.set(currentRoot, {
x: currentViewbox.x,
y: currentViewbox.y,
zoom: currentViewbox.scale
});
currentRoot = newRoot;
// Keep viewbox when replacing root elements
if (!is(newRoot, 'bpmn:SubProcess') && !storedViewbox) {
return;
}
storedViewbox = storedViewbox || { x: 0, y: 0, zoom: 1 };
var dx = (currentViewbox.x - storedViewbox.x) * currentViewbox.scale,
dy = (currentViewbox.y - storedViewbox.y) * currentViewbox.scale;
if (dx !== 0 || dy !== 0) {
canvas.scroll({
dx: dx,
dy: dy
});
}
if (storedViewbox.zoom !== currentViewbox.scale) {
canvas.zoom(storedViewbox.zoom, { x: 0, y: 0 });
}
});
eventBus.on('diagram.clear', function() {
positionMap.clear();
currentRoot = null;
});
}
DrilldownCentering.$inject = [ 'eventBus', 'canvas' ];
/**
* ES5 Map implementation. Works.
*/
function Map() {
this._entries = [];
this.set = function(key, value) {
var found = false;
for (var k in this._entries) {
if (this._entries[k][0] === key) {
this._entries[k][1] = value;
found = true;
break;
}
}
if (!found) {
this._entries.push([ key, value ]);
}
};
this.get = function(key) {
for (var k in this._entries) {
if (this._entries[k][0] === key) {
return this._entries[k][1];
}
}
return null;
};
this.clear = function() {
this._entries.length = 0;
};
this.remove = function(key) {
var idx = -1;
for (var k in this._entries) {
if (this._entries[k][0] === key) {
idx = k;
break;
}
}
if (idx !== -1) {
this._entries.splice(idx, 1);
}
};
}
================================================
FILE: lib/features/drilldown/DrilldownOverlayBehavior.js
================================================
import inherits from 'inherits-browser';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import { getBusinessObject, is } from '../../util/ModelUtil';
import { classes, domify } from 'min-dom';
import { getPlaneIdFromShape } from '../../util/DrilldownUtil';
/**
* @typedef {import('diagram-js/lib/core/Canvas').default} Canvas
* @typedef {import('diagram-js/lib/core/ElementRegistry').default} ElementRegistry
* @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
* @typedef {import('diagram-js/lib/features/overlays/Overlays').default} Overlays
* @typedef {import('diagram-js/lib/i18n/translate/translate').default} Translate
*
* @typedef {import('../../model/Types').Element} Element
* @typedef {import('../../model/Types').Parent} Parent
* @typedef {import('../../model/Types').Shape} Shape
*/
var LOW_PRIORITY = 250;
var ARROW_DOWN_SVG = ' ';
var EMPTY_MARKER = 'bjs-drilldown-empty';
/**
* @param {Canvas} canvas
* @param {EventBus} eventBus
* @param {ElementRegistry} elementRegistry
* @param {Overlays} overlays
* @param {Translate} translate
*/
export default function DrilldownOverlayBehavior(
canvas, eventBus, elementRegistry, overlays, translate
) {
CommandInterceptor.call(this, eventBus);
this._canvas = canvas;
this._eventBus = eventBus;
this._elementRegistry = elementRegistry;
this._overlays = overlays;
this._translate = translate;
var self = this;
this.executed('shape.toggleCollapse', LOW_PRIORITY, function(context) {
var shape = context.shape;
// Add overlay to the collapsed shape
if (self._canDrillDown(shape)) {
self._addOverlay(shape);
} else {
self._removeOverlay(shape);
}
}, true);
this.reverted('shape.toggleCollapse', LOW_PRIORITY, function(context) {
var shape = context.shape;
// Add overlay to the collapsed shape
if (self._canDrillDown(shape)) {
self._addOverlay(shape);
} else {
self._removeOverlay(shape);
}
}, true);
this.executed([ 'shape.create', 'shape.move', 'shape.delete' ], LOW_PRIORITY,
function(context) {
var oldParent = context.oldParent,
newParent = context.newParent || context.parent,
shape = context.shape;
// Add overlay to the collapsed shape
if (self._canDrillDown(shape)) {
self._addOverlay(shape);
}
self._updateDrilldownOverlay(oldParent);
self._updateDrilldownOverlay(newParent);
self._updateDrilldownOverlay(shape);
}, true);
this.reverted([ 'shape.create', 'shape.move', 'shape.delete' ], LOW_PRIORITY,
function(context) {
var oldParent = context.oldParent,
newParent = context.newParent || context.parent,
shape = context.shape;
// Add overlay to the collapsed shape
if (self._canDrillDown(shape)) {
self._addOverlay(shape);
}
self._updateDrilldownOverlay(oldParent);
self._updateDrilldownOverlay(newParent);
self._updateDrilldownOverlay(shape);
}, true);
eventBus.on('import.render.complete', function() {
elementRegistry.filter(function(e) {
return self._canDrillDown(e);
}).map(function(el) {
self._addOverlay(el);
});
});
}
inherits(DrilldownOverlayBehavior, CommandInterceptor);
/**
* @param {Shape} shape
*/
DrilldownOverlayBehavior.prototype._updateDrilldownOverlay = function(shape) {
var canvas = this._canvas;
if (!shape) {
return;
}
var root = canvas.findRoot(shape);
if (root) {
this._updateOverlayVisibility(root);
}
};
/**
* @param {Element} element
*
* @return {boolean}
*/
DrilldownOverlayBehavior.prototype._canDrillDown = function(element) {
var canvas = this._canvas;
return is(element, 'bpmn:SubProcess') && canvas.findRoot(getPlaneIdFromShape(element));
};
/**
* Update the visibility of the drilldown overlay. If the plane has no elements,
* the drilldown will only be shown when the element is selected.
*
* @param {Parent} element The collapsed root or shape.
*/
DrilldownOverlayBehavior.prototype._updateOverlayVisibility = function(element) {
var overlays = this._overlays;
var businessObject = getBusinessObject(element);
var overlay = overlays.get({ element: businessObject.id, type: 'drilldown' })[0];
if (!overlay) {
return;
}
var hasFlowElements = businessObject
&& businessObject.get('flowElements')
&& businessObject.get('flowElements').length;
classes(overlay.html).toggle(EMPTY_MARKER, !hasFlowElements);
};
/**
* Add a drilldown button to the given element assuming the plane has the same
* ID as the element.
*
* @param {Shape} element The collapsed shape.
*/
DrilldownOverlayBehavior.prototype._addOverlay = function(element) {
var canvas = this._canvas,
overlays = this._overlays,
bo = getBusinessObject(element);
var existingOverlays = overlays.get({ element: element, type: 'drilldown' });
if (existingOverlays.length) {
this._removeOverlay(element);
}
var button = domify('' + ARROW_DOWN_SVG + ' '),
elementName = bo.get('name') || bo.get('id'),
title = this._translate('Open {element}', { element: elementName });
button.setAttribute('title', title);
button.addEventListener('click', function() {
canvas.setRootElement(canvas.findRoot(getPlaneIdFromShape(element)));
});
overlays.add(element, 'drilldown', {
position: {
bottom: -7,
right: -8
},
html: button
});
this._updateOverlayVisibility(element);
};
DrilldownOverlayBehavior.prototype._removeOverlay = function(element) {
var overlays = this._overlays;
overlays.remove({
element: element,
type: 'drilldown'
});
};
DrilldownOverlayBehavior.$inject = [
'canvas',
'eventBus',
'elementRegistry',
'overlays',
'translate'
];
================================================
FILE: lib/features/drilldown/SubprocessCompatibility.js
================================================
import { asBounds, asTRBL } from 'diagram-js/lib/layout/LayoutUtil';
import { is, isAny } from '../../util/ModelUtil';
/**
* @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
* @typedef {import('../../model/Types').Moddle} Moddle
*
* @typedef {import('../../model/Types').Element} Element
* @typedef {import('../../model/Types').Shape} Shape
*
* @typedef {import('diagram-js/lib/core/Canvas').CanvasPlane} CanvasPlane
*
* @typedef {import('diagram-js/lib/util/Types').Rect} Rect
*/
var DEFAULT_POSITION = {
x: 180,
y: 160
};
/**
* Hook into `import.render.start` and create new planes for diagrams with
* collapsed subprocesses and all DI elements on the same plane.
*
* @param {EventBus} eventBus
* @param {Moddle} moddle
*/
export default function SubprocessCompatibility(eventBus, moddle) {
this._eventBus = eventBus;
this._moddle = moddle;
var self = this;
eventBus.on('import.render.start', 1500, function(e, context) {
self._handleImport(context.definitions);
});
}
/**
* @param {ModdleElement} definitions
*/
SubprocessCompatibility.prototype._handleImport = function(definitions) {
if (!definitions.diagrams) {
return;
}
var self = this;
this._definitions = definitions;
this._processToDiagramMap = {};
definitions.diagrams.forEach(function(diagram) {
if (!diagram.plane || !diagram.plane.bpmnElement) {
return;
}
self._processToDiagramMap[diagram.plane.bpmnElement.id] = diagram;
});
var newDiagrams = definitions.diagrams
.filter(diagram => diagram.plane)
.flatMap(diagram => self._createNewDiagrams(diagram.plane));
newDiagrams.forEach(function(diagram) {
self._movePlaneElementsToOrigin(diagram.plane);
});
};
/**
* Moves all DI elements from collapsed subprocesses to a new plane.
*
* @param {CanvasPlane} plane
*
* @return {ModdleElement[]} new diagrams created for the collapsed subprocesses
*/
SubprocessCompatibility.prototype._createNewDiagrams = function(plane) {
var self = this;
var collapsedElements = [];
var elementsToMove = [];
plane.get('planeElement').forEach(function(diElement) {
var businessObject = diElement.bpmnElement;
if (!businessObject) {
return;
}
var parent = businessObject.$parent;
if (is(businessObject, 'bpmn:SubProcess') && !diElement.isExpanded) {
collapsedElements.push(businessObject);
}
if (shouldMoveToPlane(businessObject, plane)) {
// don't change the array while we iterate over it
elementsToMove.push({ diElement: diElement, parent: parent });
}
});
var newDiagrams = [];
// create new planes for all collapsed subprocesses, even when they are empty
collapsedElements.forEach(function(element) {
if (!self._processToDiagramMap[ element.id ]) {
var diagram = self._createDiagram(element);
self._processToDiagramMap[element.id] = diagram;
newDiagrams.push(diagram);
}
});
elementsToMove.forEach(function(element) {
var diElement = element.diElement;
var parent = element.parent;
// parent is expanded, get nearest collapsed parent
while (parent && collapsedElements.indexOf(parent) === -1) {
parent = parent.$parent;
}
// false positive, all parents are expanded
if (!parent) {
return;
}
var diagram = self._processToDiagramMap[ parent.id ];
self._moveToDiPlane(diElement, diagram.plane);
});
return newDiagrams;
};
/**
* @param {CanvasPlane} plane
*/
SubprocessCompatibility.prototype._movePlaneElementsToOrigin = function(plane) {
var elements = plane.get('planeElement');
// get bounding box of all elements
var planeBounds = getPlaneBounds(plane);
var offset = {
x: planeBounds.x - DEFAULT_POSITION.x,
y: planeBounds.y - DEFAULT_POSITION.y
};
elements.forEach(function(diElement) {
if (diElement.waypoint) {
diElement.waypoint.forEach(function(waypoint) {
waypoint.x = waypoint.x - offset.x;
waypoint.y = waypoint.y - offset.y;
});
} else if (diElement.bounds) {
diElement.bounds.x = diElement.bounds.x - offset.x;
diElement.bounds.y = diElement.bounds.y - offset.y;
}
});
};
/**
* @param {ModdleElement} diElement
* @param {CanvasPlane} newPlane
*/
SubprocessCompatibility.prototype._moveToDiPlane = function(diElement, newPlane) {
var containingDiagram = findRootDiagram(diElement);
// remove DI from old Plane and add it to the new one
var parentPlaneElement = containingDiagram.plane.get('planeElement');
parentPlaneElement.splice(parentPlaneElement.indexOf(diElement), 1);
newPlane.get('planeElement').push(diElement);
};
/**
* @param {ModdleElement} businessObject
*
* @return {ModdleElement}
*/
SubprocessCompatibility.prototype._createDiagram = function(businessObject) {
var plane = this._moddle.create('bpmndi:BPMNPlane', {
bpmnElement: businessObject
});
var diagram = this._moddle.create('bpmndi:BPMNDiagram', {
plane: plane
});
plane.$parent = diagram;
plane.bpmnElement = businessObject;
diagram.$parent = this._definitions;
this._definitions.diagrams.push(diagram);
return diagram;
};
SubprocessCompatibility.$inject = [ 'eventBus', 'moddle' ];
// helpers //////////
function findRootDiagram(element) {
if (is(element, 'bpmndi:BPMNDiagram')) {
return element;
} else {
return findRootDiagram(element.$parent);
}
}
/**
* @param {CanvasPlane} plane
*
* @return {Rect}
*/
function getPlaneBounds(plane) {
var planeTrbl = {
top: Infinity,
right: -Infinity,
bottom: -Infinity,
left: Infinity
};
plane.planeElement.forEach(function(element) {
if (!element.bounds) {
return;
}
var trbl = asTRBL(element.bounds);
planeTrbl.top = Math.min(trbl.top, planeTrbl.top);
planeTrbl.left = Math.min(trbl.left, planeTrbl.left);
});
return asBounds(planeTrbl);
}
/**
* @param {ModdleElement} businessObject
* @param {CanvasPlane} plane
*
* @return {boolean}
*/
function shouldMoveToPlane(businessObject, plane) {
var parent = businessObject.$parent;
// don't move elements that are already on the plane
if (!is(parent, 'bpmn:SubProcess') || parent === plane.bpmnElement) {
return false;
}
// dataAssociations are children of the subprocess but rendered on process level
// cf. https://github.com/bpmn-io/bpmn-js/issues/1619
if (isAny(businessObject, [ 'bpmn:DataInputAssociation', 'bpmn:DataOutputAssociation' ])) {
return false;
}
return true;
}
================================================
FILE: lib/features/drilldown/index.js
================================================
import OverlaysModule from 'diagram-js/lib/features/overlays';
import ChangeSupportModule from 'diagram-js/lib/features/change-support';
import RootElementsModule from 'diagram-js/lib/features/root-elements';
import DrilldownBreadcrumbs from './DrilldownBreadcrumbs';
import DrilldownCentering from './DrilldownCentering';
import SubprocessCompatibility from './SubprocessCompatibility';
import DrilldownOverlayBehavior from './DrilldownOverlayBehavior';
export default {
__depends__: [ OverlaysModule, ChangeSupportModule, RootElementsModule ],
__init__: [ 'drilldownBreadcrumbs', 'drilldownOverlayBehavior', 'drilldownCentering', 'subprocessCompatibility' ],
drilldownBreadcrumbs: [ 'type', DrilldownBreadcrumbs ],
drilldownCentering: [ 'type', DrilldownCentering ],
drilldownOverlayBehavior: [ 'type', DrilldownOverlayBehavior ],
subprocessCompatibility: [ 'type', SubprocessCompatibility ]
};
================================================
FILE: lib/features/editor-actions/BpmnEditorActions.js
================================================
import inherits from 'inherits-browser';
import EditorActions from 'diagram-js/lib/features/editor-actions/EditorActions';
import { filter } from 'min-dash';
import { is } from '../../util/ModelUtil';
import {
getBBox
} from 'diagram-js/lib/util/Elements';
/**
* @typedef {import('didi').Injector} Injector
*/
/**
* Registers and executes BPMN specific editor actions.
*
* @param {Injector} injector
*/
export default function BpmnEditorActions(injector) {
injector.invoke(EditorActions, this);
}
inherits(BpmnEditorActions, EditorActions);
BpmnEditorActions.$inject = [
'injector'
];
/**
* Register default actions.
*
* @param {Injector} injector
*/
BpmnEditorActions.prototype._registerDefaultActions = function(injector) {
// (0) invoke super method
EditorActions.prototype._registerDefaultActions.call(this, injector);
// (1) retrieve optional components to integrate with
var canvas = injector.get('canvas', false);
var elementRegistry = injector.get('elementRegistry', false);
var selection = injector.get('selection', false);
var spaceTool = injector.get('spaceTool', false);
var lassoTool = injector.get('lassoTool', false);
var handTool = injector.get('handTool', false);
var globalConnect = injector.get('globalConnect', false);
var distributeElements = injector.get('distributeElements', false);
var alignElements = injector.get('alignElements', false);
var directEditing = injector.get('directEditing', false);
var searchPad = injector.get('searchPad', false);
var modeling = injector.get('modeling', false);
var contextPad = injector.get('contextPad', false);
// (2) check components and register actions
if (canvas && elementRegistry && selection) {
this._registerAction('selectElements', function() {
// select all elements except for the invisible
// root element
var rootElement = canvas.getRootElement();
var elements = elementRegistry.filter(function(element) {
return element !== rootElement;
});
selection.select(elements);
return elements;
});
}
if (spaceTool) {
this._registerAction('spaceTool', function() {
spaceTool.toggle();
});
}
if (lassoTool) {
this._registerAction('lassoTool', function() {
lassoTool.toggle();
});
}
if (handTool) {
this._registerAction('handTool', function() {
handTool.toggle();
});
}
if (globalConnect) {
this._registerAction('globalConnectTool', function() {
globalConnect.toggle();
});
}
if (selection && distributeElements) {
this._registerAction('distributeElements', function(opts) {
var currentSelection = selection.get(),
type = opts.type;
if (currentSelection.length) {
distributeElements.trigger(currentSelection, type);
}
});
}
if (selection && alignElements) {
this._registerAction('alignElements', function(opts) {
var currentSelection = selection.get(),
aligneableElements = [],
type = opts.type;
if (currentSelection.length) {
aligneableElements = filter(currentSelection, function(element) {
return !is(element, 'bpmn:Lane');
});
alignElements.trigger(aligneableElements, type);
}
});
}
if (selection && modeling) {
this._registerAction('setColor', function(opts) {
var currentSelection = selection.get();
if (currentSelection.length) {
modeling.setColor(currentSelection, opts);
}
});
}
if (selection && directEditing) {
this._registerAction('directEditing', function() {
var currentSelection = selection.get();
if (currentSelection.length) {
directEditing.activate(currentSelection[0]);
}
});
}
if (searchPad) {
this._registerAction('find', function() {
searchPad.toggle();
});
}
if (canvas && modeling) {
this._registerAction('moveToOrigin', function() {
var rootElement = canvas.getRootElement(),
boundingBox,
elements;
if (is(rootElement, 'bpmn:Collaboration')) {
elements = elementRegistry.filter(function(element) {
return is(element.parent, 'bpmn:Collaboration');
});
} else {
elements = elementRegistry.filter(function(element) {
return element !== rootElement && !is(element.parent, 'bpmn:SubProcess');
});
}
boundingBox = getBBox(elements);
modeling.moveElements(
elements,
{ x: -boundingBox.x, y: -boundingBox.y },
rootElement
);
});
}
if (selection && contextPad) {
this._registerAction('replaceElement', function(event) {
contextPad.triggerEntry('replace', 'click', event);
});
}
};
================================================
FILE: lib/features/editor-actions/index.js
================================================
import EditorActionsModule from 'diagram-js/lib/features/editor-actions';
import BpmnEditorActions from './BpmnEditorActions';
export default {
__depends__: [
EditorActionsModule
],
editorActions: [ 'type', BpmnEditorActions ]
};
================================================
FILE: lib/features/grid-snapping/BpmnGridSnapping.js
================================================
import { isAny } from '../modeling/util/ModelingUtil';
/**
* @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
*/
/**
* @param {EventBus} eventBus
*/
export default function BpmnGridSnapping(eventBus) {
eventBus.on([
'create.init',
'shape.move.init'
], function(event) {
var context = event.context,
shape = event.shape;
if (isAny(shape, [
'bpmn:Participant',
'bpmn:SubProcess',
'bpmn:TextAnnotation'
])) {
if (!context.gridSnappingContext) {
context.gridSnappingContext = {};
}
context.gridSnappingContext.snapLocation = 'top-left';
}
});
}
BpmnGridSnapping.$inject = [ 'eventBus' ];
================================================
FILE: lib/features/grid-snapping/behavior/GridSnappingAutoPlaceBehavior.js
================================================
import { getNewShapePosition } from '../../auto-place/BpmnAutoPlaceUtil';
import { getMid } from 'diagram-js/lib/layout/LayoutUtil';
import { is } from '../../../util/ModelUtil';
/**
* @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
* @typedef {import('diagram-js/lib/core/ElementRegistry').default} ElementRegistry
* @typedef {import('diagram-js/lib/features/grid-snapping/GridSnapping').default} GridSnapping
*
* @typedef {import('diagram-js/lib/util/Types').Axis} Axis
*/
var HIGH_PRIORITY = 2000;
/**
* @param {EventBus} eventBus
* @param {GridSnapping} gridSnapping
* @param {ElementRegistry} elementRegistry
*/
export default function GridSnappingAutoPlaceBehavior(eventBus, gridSnapping, elementRegistry) {
eventBus.on('autoPlace', HIGH_PRIORITY, function(context) {
var source = context.source,
sourceMid = getMid(source),
shape = context.shape;
var position = getNewShapePosition(source, shape, elementRegistry);
[ 'x', 'y' ].forEach(function(axis) {
var options = {};
// do not snap if x/y equal
if (position[ axis ] === sourceMid[ axis ]) {
return;
}
if (position[ axis ] > sourceMid[ axis ]) {
options.min = position[ axis ];
} else {
options.max = position[ axis ];
}
if (is(shape, 'bpmn:TextAnnotation')) {
if (isHorizontal(axis)) {
options.offset = -shape.width / 2;
} else {
options.offset = -shape.height / 2;
}
}
position[ axis ] = gridSnapping.snapValue(position[ axis ], options);
});
// must be returned to be considered by auto place
return position;
});
}
GridSnappingAutoPlaceBehavior.$inject = [
'eventBus',
'gridSnapping',
'elementRegistry'
];
// helpers //////////
/**
* @param {Axis} axis
*
* @return {boolean}
*/
function isHorizontal(axis) {
return axis === 'x';
}
================================================
FILE: lib/features/grid-snapping/behavior/GridSnappingLayoutConnectionBehavior.js
================================================
import inherits from 'inherits-browser';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import { pointsAligned } from 'diagram-js/lib/util/Geometry';
import {
assign
} from 'min-dash';
/**
* @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
* @typedef {import('diagram-js/lib/features/grid-snapping/GridSnapping').default} GridSnapping
* @typedef {import('../../modeling/Modeling').default} Modeling
*
* @typedef {import('diagram-js/lib/util/Types').Point} Point
*/
var HIGH_PRIORITY = 3000;
/**
* Snaps connections with Manhattan layout.
*
* @param {EventBus} eventBus
* @param {GridSnapping} gridSnapping
* @param {Modeling} modeling
*/
export default function GridSnappingLayoutConnectionBehavior(eventBus, gridSnapping, modeling) {
CommandInterceptor.call(this, eventBus);
this._gridSnapping = gridSnapping;
var self = this;
this.postExecuted([
'connection.create',
'connection.layout'
], HIGH_PRIORITY, function(event) {
var context = event.context,
connection = context.connection,
hints = context.hints || {},
waypoints = connection.waypoints;
if (hints.connectionStart || hints.connectionEnd || hints.createElementsBehavior === false) {
return;
}
if (!hasMiddleSegments(waypoints)) {
return;
}
modeling.updateWaypoints(connection, self.snapMiddleSegments(waypoints));
});
}
GridSnappingLayoutConnectionBehavior.$inject = [
'eventBus',
'gridSnapping',
'modeling'
];
inherits(GridSnappingLayoutConnectionBehavior, CommandInterceptor);
/**
* Snap middle segments of a given connection.
*
* @param {Point[]} waypoints
*
* @return {Point[]}
*/
GridSnappingLayoutConnectionBehavior.prototype.snapMiddleSegments = function(waypoints) {
var gridSnapping = this._gridSnapping,
snapped;
waypoints = waypoints.slice();
for (var i = 1; i < waypoints.length - 2; i++) {
snapped = snapSegment(gridSnapping, waypoints[i], waypoints[i + 1]);
waypoints[i] = snapped[0];
waypoints[i + 1] = snapped[1];
}
return waypoints;
};
// helpers //////////
/**
* Check whether a connection has a middle segments.
*
* @param {Point[]} waypoints
*
* @return {boolean}
*/
function hasMiddleSegments(waypoints) {
return waypoints.length > 3;
}
/**
* Check whether an alignment is horizontal.
*
* @param {string} aligned
*
* @return {boolean}
*/
function horizontallyAligned(aligned) {
return aligned === 'h';
}
/**
* Check whether an alignment is vertical.
*
* @param {string} aligned
*
* @return {boolean}
*/
function verticallyAligned(aligned) {
return aligned === 'v';
}
/**
* Get middle segments from a given connection.
*
* @param {Point[]} waypoints
*
* @return {Point[]}
*/
function snapSegment(gridSnapping, segmentStart, segmentEnd) {
var aligned = pointsAligned(segmentStart, segmentEnd);
var snapped = {};
if (horizontallyAligned(aligned)) {
// snap horizontally
snapped.y = gridSnapping.snapValue(segmentStart.y);
}
if (verticallyAligned(aligned)) {
// snap vertically
snapped.x = gridSnapping.snapValue(segmentStart.x);
}
if ('x' in snapped || 'y' in snapped) {
segmentStart = assign({}, segmentStart, snapped);
segmentEnd = assign({}, segmentEnd, snapped);
}
return [ segmentStart, segmentEnd ];
}
================================================
FILE: lib/features/grid-snapping/behavior/GridSnappingParticipantBehavior.js
================================================
import { is } from '../../../util/ModelUtil';
/**
* @typedef {import('diagram-js/lib/core/Canvas').default} Canvas
* @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
* @typedef {import('diagram-js/lib/features/grid-snapping/GridSnapping').default} GridSnapping
*/
var HIGHER_PRIORITY = 1750;
/**
* @param {Canvas} canvas
* @param {EventBus} eventBus
* @param {GridSnapping} gridSnapping
*/
export default function GridSnappingParticipantBehavior(canvas, eventBus, gridSnapping) {
eventBus.on([
'create.start',
'shape.move.start'
], HIGHER_PRIORITY, function(event) {
var context = event.context,
shape = context.shape,
rootElement = canvas.getRootElement();
if (!is(shape, 'bpmn:Participant') ||
!is(rootElement, 'bpmn:Process') ||
!rootElement.children.length) {
return;
}
var createConstraints = context.createConstraints;
if (!createConstraints) {
return;
}
shape.width = gridSnapping.snapValue(shape.width, { min: shape.width });
shape.height = gridSnapping.snapValue(shape.height, { min: shape.height });
});
}
GridSnappingParticipantBehavior.$inject = [
'canvas',
'eventBus',
'gridSnapping'
];
================================================
FILE: lib/features/grid-snapping/behavior/index.js
================================================
import GridSnappingAutoPlaceBehavior from './GridSnappingAutoPlaceBehavior';
import GridSnappingParticipantBehavior from './GridSnappingParticipantBehavior';
import GridSnappingLayoutConnectionBehavior from './GridSnappingLayoutConnectionBehavior';
export default {
__init__: [
'gridSnappingAutoPlaceBehavior',
'gridSnappingParticipantBehavior',
'gridSnappingLayoutConnectionBehavior',
],
gridSnappingAutoPlaceBehavior: [ 'type', GridSnappingAutoPlaceBehavior ],
gridSnappingParticipantBehavior: [ 'type', GridSnappingParticipantBehavior ],
gridSnappingLayoutConnectionBehavior: [ 'type', GridSnappingLayoutConnectionBehavior ]
};
================================================
FILE: lib/features/grid-snapping/index.js
================================================
import BpmnGridSnapping from './BpmnGridSnapping';
import GridSnappingModule from 'diagram-js/lib/features/grid-snapping';
import GridSnappingBehaviorModule from './behavior';
export default {
__depends__: [
GridSnappingModule,
GridSnappingBehaviorModule
],
__init__: [ 'bpmnGridSnapping' ],
bpmnGridSnapping: [ 'type', BpmnGridSnapping ]
};
================================================
FILE: lib/features/interaction-events/BpmnInteractionEvents.js
================================================
import { is } from '../../util/ModelUtil';
import {
isExpanded,
isHorizontal
} from '../../util/DiUtil';
/**
* @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
* @typedef {import('diagram-js/lib/features/interaction-events/InteractionEvents').default} InteractionEvents
*
* @typedef {import('../../model/Types').Element} Element
* @typedef {import('../../model/Types').Shape} Shape
*/
var LABEL_WIDTH = 30,
LABEL_HEIGHT = 30;
/**
* BPMN-specific hit zones and interaction fixes.
*
* @param {EventBus} eventBus
* @param {InteractionEvents} interactionEvents
*/
export default function BpmnInteractionEvents(eventBus, interactionEvents) {
this._interactionEvents = interactionEvents;
var self = this;
eventBus.on([
'interactionEvents.createHit',
'interactionEvents.updateHit'
], function(context) {
var element = context.element,
gfx = context.gfx;
if (is(element, 'bpmn:Lane')) {
return self._createParticipantHit(element, gfx);
} else if (is(element, 'bpmn:Participant')) {
if (isExpanded(element)) {
return self._createParticipantHit(element, gfx);
} else {
return self._createDefaultHit(element, gfx);
}
} else if (is(element, 'bpmn:SubProcess')) {
if (isExpanded(element)) {
return self._createSubProcessHit(element, gfx);
} else {
return self._createDefaultHit(element, gfx);
}
}
});
}
BpmnInteractionEvents.$inject = [
'eventBus',
'interactionEvents'
];
/**
* @param {Element} element
* @param {SVGElement} gfx
*
* @return {boolean}
*/
BpmnInteractionEvents.prototype._createDefaultHit = function(element, gfx) {
this._interactionEvents.removeHits(gfx);
this._interactionEvents.createDefaultHit(element, gfx);
// indicate that we created a hit
return true;
};
/**
* @param {Shape} element
* @param {SVGElement} gfx
*
* @return {boolean}
*/
BpmnInteractionEvents.prototype._createParticipantHit = function(element, gfx) {
// remove existing hits
this._interactionEvents.removeHits(gfx);
// add body hit
this._interactionEvents.createBoxHit(gfx, 'no-move', {
width: element.width,
height: element.height
});
// add outline hit
this._interactionEvents.createBoxHit(gfx, 'click-stroke', {
width: element.width,
height: element.height
});
// add label hit
var box = isHorizontal(element) ? {
width: LABEL_WIDTH,
height: element.height
} : {
width: element.width,
height: LABEL_HEIGHT
};
this._interactionEvents.createBoxHit(gfx, 'all', box);
// indicate that we created a hit
return true;
};
/**
* @param {Shape} element
* @param {SVGElement} gfx
*
* @return {boolean}
*/
BpmnInteractionEvents.prototype._createSubProcessHit = function(element, gfx) {
// remove existing hits
this._interactionEvents.removeHits(gfx);
// add body hit
this._interactionEvents.createBoxHit(gfx, 'no-move', {
width: element.width,
height: element.height
});
// add outline hit
this._interactionEvents.createBoxHit(gfx, 'click-stroke', {
width: element.width,
height: element.height
});
// add label hit
this._interactionEvents.createBoxHit(gfx, 'all', {
width: element.width,
height: LABEL_HEIGHT
});
// indicate that we created a hit
return true;
};
================================================
FILE: lib/features/interaction-events/index.js
================================================
import BpmnInteractionEvents from './BpmnInteractionEvents';
export default {
__init__: [ 'bpmnInteractionEvents' ],
bpmnInteractionEvents: [ 'type', BpmnInteractionEvents ]
};
================================================
FILE: lib/features/keyboard/BpmnKeyboardBindings.js
================================================
import inherits from 'inherits-browser';
import KeyboardBindings from 'diagram-js/lib/features/keyboard/KeyboardBindings';
/**
* @typedef {import('didi').Injector} Injector
* @typedef {import('diagram-js/lib/features/editor-actions/EditorActions').default} EditorActions
* @typedef {import('diagram-js/lib/features/keyboard/Keyboard').default} Keyboard
*/
/**
* BPMN 2.0 specific keyboard bindings.
*
* @param {Injector} injector
*/
export default function BpmnKeyboardBindings(injector) {
injector.invoke(KeyboardBindings, this);
}
inherits(BpmnKeyboardBindings, KeyboardBindings);
BpmnKeyboardBindings.$inject = [
'injector'
];
/**
* Register available keyboard bindings.
*
* @param {Keyboard} keyboard
* @param {EditorActions} editorActions
*/
BpmnKeyboardBindings.prototype.registerBindings = function(keyboard, editorActions) {
// inherit default bindings
KeyboardBindings.prototype.registerBindings.call(this, keyboard, editorActions);
/**
* Add keyboard binding if respective editor action
* is registered.
*
* @param {string} action name
* @param {Function} fn that implements the key binding
*/
function addListener(action, fn) {
if (editorActions.isRegistered(action)) {
keyboard.addListener(fn);
}
}
// select all elements
// CTRL + A
addListener('selectElements', function(context) {
var event = context.keyEvent;
if (keyboard.isKey([ 'a', 'A' ], event) && keyboard.isCmd(event)) {
editorActions.trigger('selectElements');
return true;
}
});
// search labels
// CTRL + F
addListener('find', function(context) {
var event = context.keyEvent;
if (keyboard.isKey([ 'f', 'F' ], event) && keyboard.isCmd(event)) {
editorActions.trigger('find');
return true;
}
});
// activate space tool
// S
addListener('spaceTool', function(context) {
var event = context.keyEvent;
if (keyboard.hasModifier(event)) {
return;
}
if (keyboard.isKey([ 's', 'S' ], event)) {
editorActions.trigger('spaceTool');
return true;
}
});
// activate lasso tool
// L
addListener('lassoTool', function(context) {
var event = context.keyEvent;
if (keyboard.hasModifier(event)) {
return;
}
if (keyboard.isKey([ 'l', 'L' ], event)) {
editorActions.trigger('lassoTool');
return true;
}
});
// activate hand tool
// H
addListener('handTool', function(context) {
var event = context.keyEvent;
if (keyboard.hasModifier(event)) {
return;
}
if (keyboard.isKey([ 'h', 'H' ], event)) {
editorActions.trigger('handTool');
return true;
}
});
// activate global connect tool
// C
addListener('globalConnectTool', function(context) {
var event = context.keyEvent;
if (keyboard.hasModifier(event)) {
return;
}
if (keyboard.isKey([ 'c', 'C' ], event)) {
editorActions.trigger('globalConnectTool');
return true;
}
});
// activate direct editing
// E
addListener('directEditing', function(context) {
var event = context.keyEvent;
if (keyboard.hasModifier(event)) {
return;
}
if (keyboard.isKey([ 'e', 'E' ], event)) {
editorActions.trigger('directEditing');
return true;
}
});
// activate replace element
// R
addListener('replaceElement', function(context) {
var event = context.keyEvent;
if (keyboard.hasModifier(event)) {
return;
}
if (keyboard.isKey([ 'r', 'R' ], event)) {
editorActions.trigger('replaceElement', event);
return true;
}
});
};
================================================
FILE: lib/features/keyboard/index.js
================================================
import KeyboardModule from 'diagram-js/lib/features/keyboard';
import BpmnKeyboardBindings from './BpmnKeyboardBindings';
export default {
__depends__: [
KeyboardModule
],
__init__: [ 'keyboardBindings' ],
keyboardBindings: [ 'type', BpmnKeyboardBindings ]
};
================================================
FILE: lib/features/label-editing/LabelEditingPreview.js
================================================
import {
append as svgAppend,
attr as svgAttr,
create as svgCreate,
remove as svgRemove
} from 'tiny-svg';
import {
getDi,
is
} from '../../util/ModelUtil';
import {
translate
} from 'diagram-js/lib/util/SvgTransformUtil';
/**
* @typedef {import('diagram-js/lib/core/Canvas').default} Canvas
* @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
* @typedef {import('../../draw/PathMap').default} PathMap
*/
var MARKER_HIDDEN = 'djs-element-hidden',
MARKER_LABEL_HIDDEN = 'djs-label-hidden';
/**
* @param {EventBus} eventBus
* @param {Canvas} canvas
* @param {PathMap} pathMap
*/
export default function LabelEditingPreview(eventBus, canvas, pathMap) {
var self = this;
var defaultLayer = canvas.getDefaultLayer();
var element, absoluteElementBBox, gfx;
eventBus.on('directEditing.activate', function(context) {
var activeProvider = context.active;
element = activeProvider.element.label || activeProvider.element;
// text annotation
if (is(element, 'bpmn:TextAnnotation')) {
absoluteElementBBox = canvas.getAbsoluteBBox(element);
gfx = svgCreate('g');
var textPathData = pathMap.getScaledPath('TEXT_ANNOTATION', {
xScaleFactor: 1,
yScaleFactor: 1,
containerWidth: element.width,
containerHeight: element.height,
position: {
mx: 0.0,
my: 0.0
}
});
var path = self.path = svgCreate('path');
svgAttr(path, {
d: textPathData,
strokeWidth: 2,
stroke: getStrokeColor(element)
});
svgAppend(gfx, path);
svgAppend(defaultLayer, gfx);
translate(gfx, element.x, element.y);
}
if (is(element, 'bpmn:TextAnnotation') ||
element.labelTarget) {
canvas.addMarker(element, MARKER_HIDDEN);
} else if (is(element, 'bpmn:Task') ||
is(element, 'bpmn:CallActivity') ||
is(element, 'bpmn:SubProcess') ||
is(element, 'bpmn:Participant') ||
is(element, 'bpmn:Lane')) {
canvas.addMarker(element, MARKER_LABEL_HIDDEN);
}
});
eventBus.on('directEditing.resize', function(context) {
// text annotation
if (is(element, 'bpmn:TextAnnotation')) {
var height = context.height,
dy = context.dy;
var newElementHeight = Math.max(element.height / absoluteElementBBox.height * (height + dy), 0);
var textPathData = pathMap.getScaledPath('TEXT_ANNOTATION', {
xScaleFactor: 1,
yScaleFactor: 1,
containerWidth: element.width,
containerHeight: newElementHeight,
position: {
mx: 0.0,
my: 0.0
}
});
svgAttr(self.path, {
d: textPathData
});
}
});
eventBus.on([ 'directEditing.complete', 'directEditing.cancel' ], function(context) {
var activeProvider = context.active;
if (activeProvider) {
canvas.removeMarker(activeProvider.element.label || activeProvider.element, MARKER_HIDDEN);
canvas.removeMarker(element, MARKER_LABEL_HIDDEN);
}
element = undefined;
absoluteElementBBox = undefined;
if (gfx) {
svgRemove(gfx);
gfx = undefined;
}
});
}
LabelEditingPreview.$inject = [
'eventBus',
'canvas',
'pathMap'
];
// helpers //////////
function getStrokeColor(element, defaultColor) {
var di = getDi(element);
return di.get('stroke') || defaultColor || 'black';
}
================================================
FILE: lib/features/label-editing/LabelEditingProvider.js
================================================
import {
assign
} from 'min-dash';
import {
getLabel
} from '../../util/LabelUtil';
import {
is
} from '../../util/ModelUtil';
import { isAny } from '../modeling/util/ModelingUtil';
import {
isExpanded,
isHorizontal
} from '../../util/DiUtil';
import {
getExternalLabelMid,
isLabelExternal,
hasExternalLabel,
isLabel
} from '../../util/LabelUtil';
/**
* @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
* @typedef {import('../modeling/BpmnFactory').default} BpmnFactory
* @typedef {import('diagram-js/lib/core/Canvas').default} Canvas
* @typedef {import('diagram-js-direct-editing/lib/DirectEditing').default} DirectEditing
* @typedef {import('../modeling/Modeling').default} Modeling
* @typedef {import('diagram-js/lib/features/resize/ResizeHandles').default} ResizeHandles
* @typedef {import('../../draw/TextRenderer').default} TextRenderer
*
* @typedef {import('../../model/Types').Element} Element
*
* @typedef { {
* bounds: {
* x: number;
* y: number;
* width: number;
* height: number;
* minWidth?: number;
* minHeight?: number;
* };
* style: Object;
* } } DirectEditingContext
*/
var HIGH_PRIORITY = 2000;
/**
* @param {EventBus} eventBus
* @param {BpmnFactory} bpmnFactory
* @param {Canvas} canvas
* @param {DirectEditing} directEditing
* @param {Modeling} modeling
* @param {ResizeHandles} resizeHandles
* @param {TextRenderer} textRenderer
*/
export default function LabelEditingProvider(
eventBus, bpmnFactory, canvas, directEditing,
modeling, resizeHandles, textRenderer) {
this._bpmnFactory = bpmnFactory;
this._canvas = canvas;
this._modeling = modeling;
this._textRenderer = textRenderer;
directEditing.registerProvider(this);
// listen to dblclick on non-root elements
eventBus.on('element.dblclick', function(event) {
activateDirectEdit(event.element, true);
});
// complete on followup canvas operation
eventBus.on([
'autoPlace.start',
'canvas.viewbox.changing',
'drag.init',
'element.mousedown',
'popupMenu.open',
'root.set',
'selection.changed'
], function() {
if (directEditing.isActive()) {
directEditing.complete();
}
});
eventBus.on([
'shape.remove',
'connection.remove'
], HIGH_PRIORITY, function(event) {
if (directEditing.isActive(event.element)) {
directEditing.cancel();
}
});
// cancel on command stack changes
eventBus.on([ 'commandStack.changed' ], function(e) {
if (directEditing.isActive()) {
directEditing.cancel();
}
});
eventBus.on('directEditing.activate', function(event) {
resizeHandles.removeResizers();
});
eventBus.on('create.end', 500, function(event) {
var context = event.context,
element = context.shape,
canExecute = event.context.canExecute,
isTouch = event.isTouch;
// TODO(nikku): we need to find a way to support the
// direct editing on mobile devices; right now this will
// break for desworkflowediting on mobile devices
// as it breaks the user interaction workflow
// TODO(nikku): we should temporarily focus the edited element
// here and release the focused viewport after the direct edit
// operation is finished
if (isTouch) {
return;
}
if (!canExecute) {
return;
}
if (context.hints && context.hints.createElementsBehavior === false) {
return;
}
activateDirectEdit(element);
});
eventBus.on('autoPlace.end', 500, function(event) {
activateDirectEdit(event.shape);
});
function activateDirectEdit(element, force) {
if (force ||
isAny(element, [ 'bpmn:Task', 'bpmn:TextAnnotation', 'bpmn:Participant' ]) ||
isCollapsedSubProcess(element)) {
directEditing.activate(element);
}
}
}
LabelEditingProvider.$inject = [
'eventBus',
'bpmnFactory',
'canvas',
'directEditing',
'modeling',
'resizeHandles',
'textRenderer'
];
/**
* Activate direct editing for activities and text annotations.
*
* @param {Element} element
*
* @return { {
* text: string;
* options?: {
* autoResize?: boolean;
* centerVertically?: boolean;
* resizable?: boolean;
* }
* } & DirectEditingContext }
*/
LabelEditingProvider.prototype.activate = function(element) {
// text
var text = getLabel(element);
if (text === undefined) {
return;
}
var context = {
text: text
};
// bounds
var bounds = this.getEditingBBox(element);
assign(context, bounds);
var options = {};
var style = context.style || {};
// Remove background and border
assign(style, {
backgroundColor: null,
border: null
});
// tasks
if (
isAny(element, [
'bpmn:Task',
'bpmn:Participant',
'bpmn:Lane',
'bpmn:CallActivity'
]) ||
isCollapsedSubProcess(element)
) {
assign(options, {
centerVertically: true
});
}
// external labels
if (isLabelExternal(element)) {
assign(options, {
autoResize: true
});
// keep background and border for external labels
assign(style, {
backgroundColor: '#ffffff',
border: '1px solid #ccc'
});
}
// text annotations
if (is(element, 'bpmn:TextAnnotation')) {
assign(options, {
resizable: true,
autoResize: true
});
// keep background and border for text annotations
assign(style, {
backgroundColor: '#ffffff',
border: '1px solid #ccc'
});
}
assign(context, {
options: options,
style: style
});
return context;
};
/**
* Get the editing bounding box based on the element's size and position.
*
* @param {Element} element
*
* @return {DirectEditingContext}
*/
LabelEditingProvider.prototype.getEditingBBox = function(element) {
var canvas = this._canvas;
var target = element.label || element;
var bbox = canvas.getAbsoluteBBox(target);
var mid = {
x: bbox.x + bbox.width / 2,
y: bbox.y + bbox.height / 2
};
// default position
var bounds = { x: bbox.x, y: bbox.y };
var zoom = canvas.zoom();
var defaultStyle = this._textRenderer.getDefaultStyle(),
externalStyle = this._textRenderer.getExternalStyle();
// take zoom into account
var externalFontSize = externalStyle.fontSize * zoom,
externalLineHeight = externalStyle.lineHeight,
defaultFontSize = defaultStyle.fontSize * zoom,
defaultLineHeight = defaultStyle.lineHeight;
var style = {
fontFamily: this._textRenderer.getDefaultStyle().fontFamily,
fontWeight: this._textRenderer.getDefaultStyle().fontWeight
};
// adjust for expanded pools AND lanes
if (is(element, 'bpmn:Lane') || isExpandedPool(element)) {
var isHorizontalLane = isHorizontal(element);
var laneBounds = isHorizontalLane ? {
width: bbox.height,
height: 30 * zoom,
x: bbox.x - bbox.height / 2 + (15 * zoom),
y: mid.y - (30 * zoom) / 2
} : {
width: bbox.width,
height: 30 * zoom
};
assign(bounds, laneBounds);
assign(style, {
fontSize: defaultFontSize + 'px',
lineHeight: defaultLineHeight,
paddingTop: (7 * zoom) + 'px',
paddingBottom: (7 * zoom) + 'px',
paddingLeft: (5 * zoom) + 'px',
paddingRight: (5 * zoom) + 'px',
transform: isHorizontalLane ? 'rotate(-90deg)' : null
});
}
// internal labels for collapsed participants
if (isCollapsedPool(element)) {
var isHorizontalPool = isHorizontal(element);
var poolBounds = isHorizontalPool ? {
width: bbox.width,
height: bbox.height
} : {
width: bbox.height,
height: bbox.width,
x: mid.x - bbox.height / 2,
y: mid.y - bbox.width / 2
};
assign(bounds, poolBounds);
assign(style, {
fontSize: defaultFontSize + 'px',
lineHeight: defaultLineHeight,
paddingTop: (7 * zoom) + 'px',
paddingBottom: (7 * zoom) + 'px',
paddingLeft: (5 * zoom) + 'px',
paddingRight: (5 * zoom) + 'px',
transform: isHorizontalPool ? null : 'rotate(-90deg)'
});
}
// internal labels for tasks and collapsed call activities
// and sub processes
if (isAny(element, [ 'bpmn:Task', 'bpmn:CallActivity' ]) ||
isCollapsedSubProcess(element)) {
assign(bounds, {
width: bbox.width,
height: bbox.height
});
assign(style, {
fontSize: defaultFontSize + 'px',
lineHeight: defaultLineHeight,
paddingTop: (7 * zoom) + 'px',
paddingBottom: (7 * zoom) + 'px',
paddingLeft: (5 * zoom) + 'px',
paddingRight: (5 * zoom) + 'px'
});
}
// internal labels for expanded sub processes
if (isExpandedSubProcess(element)) {
assign(bounds, {
width: bbox.width,
x: bbox.x
});
assign(style, {
fontSize: defaultFontSize + 'px',
lineHeight: defaultLineHeight,
paddingTop: (7 * zoom) + 'px',
paddingBottom: (7 * zoom) + 'px',
paddingLeft: (5 * zoom) + 'px',
paddingRight: (5 * zoom) + 'px'
});
}
var width = 90 * zoom,
paddingTop = 7 * zoom,
paddingBottom = 4 * zoom;
// external labels for events, data elements, gateways, groups and connections
if (target.labelTarget) {
assign(bounds, {
width: width,
height: bbox.height + paddingTop + paddingBottom,
x: mid.x - width / 2,
y: bbox.y - paddingTop
});
assign(style, {
fontSize: externalFontSize + 'px',
lineHeight: externalLineHeight,
paddingTop: paddingTop + 'px',
paddingBottom: paddingBottom + 'px'
});
}
// external label not yet created
if (isLabelExternal(target)
&& !hasExternalLabel(target)
&& !isLabel(target)) {
var externalLabelMid = getExternalLabelMid(element);
var absoluteBBox = canvas.getAbsoluteBBox({
x: externalLabelMid.x,
y: externalLabelMid.y,
width: 0,
height: 0
});
var height = externalFontSize + paddingTop + paddingBottom;
assign(bounds, {
width: width,
height: height,
x: absoluteBBox.x - width / 2,
y: absoluteBBox.y - height / 2
});
assign(style, {
fontSize: externalFontSize + 'px',
lineHeight: externalLineHeight,
paddingTop: paddingTop + 'px',
paddingBottom: paddingBottom + 'px'
});
}
// text annotations
if (is(element, 'bpmn:TextAnnotation')) {
assign(bounds, {
width: bbox.width,
height: bbox.height,
minWidth: 30 * zoom,
minHeight: 10 * zoom
});
assign(style, {
textAlign: 'left',
paddingTop: (5 * zoom) + 'px',
paddingBottom: (7 * zoom) + 'px',
paddingLeft: (7 * zoom) + 'px',
paddingRight: (5 * zoom) + 'px',
fontSize: defaultFontSize + 'px',
lineHeight: defaultLineHeight
});
}
return { bounds: bounds, style: style };
};
LabelEditingProvider.prototype.update = function(
element, newLabel,
activeContextText, bounds) {
var newBounds,
bbox;
if (is(element, 'bpmn:TextAnnotation')) {
bbox = this._canvas.getAbsoluteBBox(element);
newBounds = {
x: element.x,
y: element.y,
width: element.width / bbox.width * bounds.width,
height: element.height / bbox.height * bounds.height
};
}
if (isEmptyText(newLabel)) {
newLabel = null;
}
this._modeling.updateLabel(element, newLabel, newBounds);
};
// helpers //////////
function isCollapsedSubProcess(element) {
return is(element, 'bpmn:SubProcess') && !isExpanded(element);
}
function isExpandedSubProcess(element) {
return is(element, 'bpmn:SubProcess') && isExpanded(element);
}
function isCollapsedPool(element) {
return is(element, 'bpmn:Participant') && !isExpanded(element);
}
function isExpandedPool(element) {
return is(element, 'bpmn:Participant') && isExpanded(element);
}
function isEmptyText(label) {
return !label || !label.trim();
}
================================================
FILE: lib/features/label-editing/LabelUtil.js
================================================
export {
getLabel,
setLabel
} from '../../util/LabelUtil';
================================================
FILE: lib/features/label-editing/cmd/UpdateLabelHandler.js
================================================
import {
setLabel,
getLabel
} from '../../../util/LabelUtil';
import {
getExternalLabelMid,
isLabelExternal,
hasExternalLabel,
isLabel
} from '../../../util/LabelUtil';
import {
is
} from '../../../util/ModelUtil';
var NULL_DIMENSIONS = {
width: 0,
height: 0
};
/**
* @typedef {import('../../modeling/Modeling').default} Modeling
* @typedef {import('../../../draw/TextRenderer').default} TextRenderer
* @typedef {import('../../modeling/BpmnFactory').default} BpmnFactory
*
* @typedef {import('../../../model/Types').Element} Element
*/
/**
* A handler that updates the text of a BPMN element.
*
* @param {Modeling} modeling
* @param {TextRenderer} textRenderer
* @param {BpmnFactory} bpmnFactory
*/
export default function UpdateLabelHandler(modeling, textRenderer, bpmnFactory) {
/**
* Set the label and return the changed elements.
*
* Element parameter can be label itself or connection (i.e. sequence flow).
*
* @param {Element} element
* @param {string} text
*/
function setText(element, text) {
// external label if present
var label = element.label || element;
var labelTarget = element.labelTarget || element;
setLabel(label, text, labelTarget !== label);
return [ label, labelTarget ];
}
function preExecute(ctx) {
var element = ctx.element,
businessObject = element.businessObject,
newLabel = ctx.newLabel;
if (!isLabel(element)
&& isLabelExternal(element)
&& !hasExternalLabel(element)
&& !isEmptyText(newLabel)) {
// create label
var paddingTop = 7;
var labelCenter = getExternalLabelMid(element);
labelCenter = {
x: labelCenter.x,
y: labelCenter.y + paddingTop
};
modeling.createLabel(element, labelCenter, {
id: businessObject.id + '_label',
businessObject: businessObject,
di: element.di
});
}
}
function execute(ctx) {
ctx.oldLabel = getLabel(ctx.element);
return setText(ctx.element, ctx.newLabel);
}
function revert(ctx) {
return setText(ctx.element, ctx.oldLabel);
}
function postExecute(ctx) {
var element = ctx.element,
label = element.label || element,
newLabel = ctx.newLabel,
newBounds = ctx.newBounds,
hints = ctx.hints || {};
// ignore internal labels for elements except text annotations
if (!isLabel(label) && !is(label, 'bpmn:TextAnnotation')) {
return;
}
if (isLabel(label) && isEmptyText(newLabel)) {
if (hints.removeShape !== false) {
modeling.removeShape(label, { unsetLabel: false });
}
return;
}
var text = getLabel(element);
// resize element based on label _or_ pre-defined bounds
if (typeof newBounds === 'undefined') {
newBounds = textRenderer.getExternalLabelBounds(label, text);
}
// setting newBounds to false or _null_ will
// disable the postExecute resize operation
if (newBounds) {
modeling.resizeShape(label, newBounds, NULL_DIMENSIONS);
}
}
// API
this.preExecute = preExecute;
this.execute = execute;
this.revert = revert;
this.postExecute = postExecute;
}
UpdateLabelHandler.$inject = [
'modeling',
'textRenderer',
'bpmnFactory'
];
// helpers //////////
function isEmptyText(label) {
return !label || !label.trim();
}
================================================
FILE: lib/features/label-editing/index.js
================================================
import ChangeSupportModule from 'diagram-js/lib/features/change-support';
import ResizeModule from 'diagram-js/lib/features/resize';
import DirectEditingModule from 'diagram-js-direct-editing';
import LabelEditingProvider from './LabelEditingProvider';
import LabelEditingPreview from './LabelEditingPreview';
export default {
__depends__: [
ChangeSupportModule,
ResizeModule,
DirectEditingModule
],
__init__: [
'labelEditingProvider',
'labelEditingPreview'
],
labelEditingProvider: [ 'type', LabelEditingProvider ],
labelEditingPreview: [ 'type', LabelEditingPreview ]
};
================================================
FILE: lib/features/label-link/LabelLink.js
================================================
import { queryAll as domQueryAll } from 'min-dom';
import {
append as svgAppend,
attr as svgAttr,
remove as svgRemove,
} from 'tiny-svg';
import { createLine, updateLine } from 'diagram-js/lib/util/RenderUtil';
import { getMid, getElementLineIntersection } from 'diagram-js/lib/layout/LayoutUtil';
import { getDistancePointPoint } from 'diagram-js/lib/features/bendpoints/GeometricUtil';
import { isLabel } from 'diagram-js/lib/util/ModelUtil';
import { isAny } from '../modeling/util/ModelingUtil';
import { getRoundRectPath, getCirclePath } from '../../draw/BpmnRenderUtil';
/**
* @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
* @typedef {import('diagram-js/lib/core/Canvas').default} Canvas
* @typedef {import('diagram-js/lib/core/GraphicsFactory').default} GraphicsFactory
* @typedef {import('../outline/OutlineProvider').default} Outline
* @typedef {import('diagram-js/lib/features/selection').default} Selection
*
* @typedef {import('diagram-js/lib/model/Types').Element} Element
*/
const ALLOWED_ELEMENTS = [ 'bpmn:Event', 'bpmn:SequenceFlow', 'bpmn:Gateway' ];
const LINE_STYLE = {
class: 'bjs-label-link',
stroke: 'var(--element-selected-outline-secondary-stroke-color)',
strokeDasharray: '5, 5',
};
const DISTANCE_THRESHOLD = 15;
const PATH_OFFSET = 2;
/**
* Render a line between an external label and its target element,
* when either is selected.
*
* @param {EventBus} eventBus
* @param {Canvas} canvas
* @param {GraphicsFactory} graphicsFactory
* @param {Outline} outline
*/
export default function LabelLink(eventBus, canvas, graphicsFactory, outline, selection) {
const layer = canvas.getLayer('overlays');
eventBus.on([ 'selection.changed', 'shape.changed' ], function() {
cleanUp();
});
eventBus.on('selection.changed', function({ newSelection }) {
const allowedElements = newSelection.filter(element => isAny(element, ALLOWED_ELEMENTS));
if (allowedElements.length === 1) {
const element = allowedElements[0];
if (isLabel(element)) {
createLink(element, element.labelTarget, newSelection);
} else if (element.labels?.length) {
createLink(element.labels[0], element, newSelection);
}
}
// Only allowed when both label and its target are selected
if (allowedElements.length === 2) {
const label = allowedElements.find(isLabel);
const target = allowedElements.find(el => el.labels?.includes(label));
if (label && target) {
createLink(label, target, newSelection);
}
}
});
eventBus.on('shape.changed', function({ element }) {
if (!isAny(element, ALLOWED_ELEMENTS) || !isElementSelected(element)) {
return;
}
if (isLabel(element)) {
createLink(element, element.labelTarget, selection.get());
} else if (element.labels?.length) {
createLink(element.labels[0], element, selection.get());
}
});
/**
* Render a line between an external label and its target.
*
* @param {Element} label
* @param {Element} target
* @param {Element[]} selection
*/
function createLink(label, target, selection = []) {
// Create an auxiliary line between label and target mid points
const line = createLine(
[ getMid(target), getMid(label) ],
LINE_STYLE
);
const linePath = line.getAttribute('d');
// Calculate the intersection point between line and label
const labelSelected = selection.includes(label);
const labelPath = labelSelected ? getElementOutlinePath(label) : getElementPath(label);
const labelInter = getElementLineIntersection(labelPath, linePath);
// Label on top of the target
if (!labelInter) {
return;
}
// Calculate the intersection point between line and label
// If the target is a sequence flow, there is no intersection,
// so we link to the middle of it.
const targetSelected = selection.includes(target);
const targetPath = targetSelected ? getElementOutlinePath(target) : getElementPath(target);
const targetInter = getElementLineIntersection(targetPath, linePath) || getMid(target);
// Do not draw a link if the points are too close
const distance = getDistancePointPoint(targetInter, labelInter);
if (distance < DISTANCE_THRESHOLD) {
return;
}
// Connect the actual closest points
updateLine(line, [ targetInter, labelInter ]);
svgAppend(layer, line);
}
/**
* Remove all existing label links.
*/
function cleanUp() {
domQueryAll(`.${LINE_STYLE.class}`, layer).forEach(svgRemove);
}
/**
* Get element's slightly expanded outline path.
*
* @param {Element} element
* @returns {string} svg path
*/
function getElementOutlinePath(element) {
const outlineShape = outline.getOutline(element);
const outlineOffset = outline.offset;
if (!outlineShape) {
return getElementPath(element);
}
if (outlineShape.x) {
const shape = {
x: element.x + parseSvgNumAttr(outlineShape, 'x') - PATH_OFFSET,
y: element.y + parseSvgNumAttr(outlineShape, 'y') - PATH_OFFSET,
width: parseSvgNumAttr(outlineShape, 'width') + PATH_OFFSET * 2,
height: parseSvgNumAttr(outlineShape, 'height') + PATH_OFFSET * 2
};
return getRoundRectPath(shape, parseSvgNumAttr(outlineShape, 'rx'));
}
if (outlineShape.cx) {
const shape = {
x: element.x - outlineOffset,
y: element.y - outlineOffset,
width: parseSvgNumAttr(outlineShape, 'r') * 2,
height: parseSvgNumAttr(outlineShape, 'r') * 2,
};
return getCirclePath(shape);
}
}
function getElementPath(element) {
return graphicsFactory.getShapePath(element);
}
function isElementSelected(element) {
return selection.get().includes(element);
}
}
LabelLink.$inject = [
'eventBus',
'canvas',
'graphicsFactory',
'outline',
'selection'
];
/**
* Get numeric attribute from SVG element
* or 0 if not present.
*
* @param {SVGElement} node
* @param {string} attr
* @returns {number}
*/
function parseSvgNumAttr(node, attr) {
return parseFloat(svgAttr(node, attr) || 0);
}
================================================
FILE: lib/features/label-link/index.js
================================================
import SelectionModule from 'diagram-js/lib/features/selection';
import OutlineModule from 'diagram-js/lib/features/outline';
import LabelLink from './LabelLink';
export default {
__depends__: [
SelectionModule,
OutlineModule
],
__init__: [
'labelLink'
],
labelLink: [ 'type', LabelLink ]
};
================================================
FILE: lib/features/modeling/BpmnFactory.js
================================================
import {
map,
assign,
pick
} from 'min-dash';
import {
isAny
} from './util/ModelingUtil';
import {
is
} from '../../util/ModelUtil';
/**
* @typedef {import('../../model/Types').Moddle} Moddle
* @typedef {import('../../model/Types').ModdleElement} ModdleElement
*
* @typedef {import('diagram-js/lib/util/Types').Point} Point
*/
/**
* A factory for BPMN elements.
*
* @param {Moddle} moddle
*/
export default function BpmnFactory(moddle) {
this._model = moddle;
}
BpmnFactory.$inject = [ 'moddle' ];
/**
* @param {ModdleElement} element
*
* @return {boolean}
*/
BpmnFactory.prototype._needsId = function(element) {
return isAny(element, [
'bpmn:RootElement',
'bpmn:FlowElement',
'bpmn:MessageFlow',
'bpmn:DataAssociation',
'bpmn:Artifact',
'bpmn:Participant',
'bpmn:Lane',
'bpmn:LaneSet',
'bpmn:Process',
'bpmn:Collaboration',
'bpmndi:BPMNShape',
'bpmndi:BPMNEdge',
'bpmndi:BPMNDiagram',
'bpmndi:BPMNPlane',
'bpmn:Property',
'bpmn:CategoryValue'
]);
};
/**
* @param {ModdleElement} element
*/
BpmnFactory.prototype._ensureId = function(element) {
if (element.id) {
this._model.ids.claim(element.id, element);
return;
}
// generate semantic ids for elements
// bpmn:SequenceFlow -> SequenceFlow_ID
var prefix;
if (is(element, 'bpmn:Activity')) {
prefix = 'Activity';
} else if (is(element, 'bpmn:Event')) {
prefix = 'Event';
} else if (is(element, 'bpmn:Gateway')) {
prefix = 'Gateway';
} else if (isAny(element, [ 'bpmn:SequenceFlow', 'bpmn:MessageFlow' ])) {
prefix = 'Flow';
} else {
prefix = (element.$type || '').replace(/^[^:]*:/g, '');
}
prefix += '_';
if (!element.id && this._needsId(element)) {
element.id = this._model.ids.nextPrefixed(prefix, element);
}
};
/**
* Create BPMN element.
*
* @param {string} type
* @param {Object} [attrs]
*
* @return {ModdleElement}
*/
BpmnFactory.prototype.create = function(type, attrs) {
var element = this._model.create(type, attrs || {});
this._ensureId(element);
return element;
};
/**
* @return {ModdleElement}
*/
BpmnFactory.prototype.createDiLabel = function() {
return this.create('bpmndi:BPMNLabel', {
bounds: this.createDiBounds()
});
};
/**
* @param {ModdleElement} semantic
* @param {Object} [attrs]
* @return {ModdleElement}
*/
BpmnFactory.prototype.createDiShape = function(semantic, attrs) {
return this.create('bpmndi:BPMNShape', assign({
bpmnElement: semantic,
bounds: this.createDiBounds()
}, attrs));
};
/**
* @return {ModdleElement}
*/
BpmnFactory.prototype.createDiBounds = function(bounds) {
return this.create('dc:Bounds', bounds);
};
/**
* @param {Point[]} waypoints
*
* @return {ModdleElement[]}
*/
BpmnFactory.prototype.createDiWaypoints = function(waypoints) {
var self = this;
return map(waypoints, function(pos) {
return self.createDiWaypoint(pos);
});
};
/**
* @param {Point} point
*
* @return {ModdleElement}
*/
BpmnFactory.prototype.createDiWaypoint = function(point) {
return this.create('dc:Point', pick(point, [ 'x', 'y' ]));
};
/**
* @param {ModdleElement} semantic
* @param {Object} [attrs]
*
* @return {ModdleElement}
*/
BpmnFactory.prototype.createDiEdge = function(semantic, attrs) {
return this.create('bpmndi:BPMNEdge', assign({
bpmnElement: semantic,
waypoint: this.createDiWaypoints([])
}, attrs));
};
/**
* @param {ModdleElement} semantic
* @param {Object} [attrs]
*
* @return {ModdleElement}
*/
BpmnFactory.prototype.createDiPlane = function(semantic, attrs) {
return this.create('bpmndi:BPMNPlane', assign({
bpmnElement: semantic
}, attrs));
};
================================================
FILE: lib/features/modeling/BpmnLayouter.js
================================================
import inherits from 'inherits-browser';
import {
assign
} from 'min-dash';
import BaseLayouter from 'diagram-js/lib/layout/BaseLayouter';
import {
repairConnection,
withoutRedundantPoints
} from 'diagram-js/lib/layout/ManhattanLayout';
import {
getMid,
getOrientation
} from 'diagram-js/lib/layout/LayoutUtil';
import {
isExpanded
} from '../../util/DiUtil';
import { is } from '../../util/ModelUtil';
import { isDirectionHorizontal } from './util/ModelingUtil';
/**
* @typedef {import('diagram-js/lib/core/ElementRegistry').default} ElementRegistry
*
* @typedef {import('diagram-js/lib/util/Types').Point} Point
*
* @typedef {import('../../model/Types').Connection} Connection
* @typedef {import('../../model/Types').Element} Element
*
* @typedef {import('diagram-js/lib/layout/BaseLayouter').LayoutConnectionHints} LayoutConnectionHints
*
* @typedef { {
* source?: Element;
* target?: Element;
* waypoints?: Point[];
* connectionStart?: Point;
* connectionEnd?: Point;
* } & LayoutConnectionHints } BpmnLayoutConnectionHints
*/
var ATTACH_ORIENTATION_PADDING = -10,
BOUNDARY_TO_HOST_THRESHOLD = 40;
// layout all connection between flow elements h:h, except for
// (1) outgoing of boundary events -> layout based on attach orientation and target orientation
// (2) incoming/outgoing of gateways -> v:h for outgoing, h:v for incoming
// (3) loops connect sides clockwise
var PREFERRED_LAYOUTS_HORIZONTAL = {
default: [ 'h:h' ],
fromGateway: [ 'v:h' ],
toGateway: [ 'h:v' ],
loop: {
fromTop: [ 't:r' ],
fromRight: [ 'r:b' ],
fromLeft: [ 'l:t' ],
fromBottom: [ 'b:l' ]
},
boundaryLoop: {
alternateHorizontalSide: 'b',
alternateVerticalSide: 'l',
default: 'v'
},
messageFlow: [ 'straight', 'v:v' ],
subProcess: [ 'straight', 'h:h' ],
isHorizontal: true
};
// for vertical layouts, switch h and v and loop counter-clockwise
var PREFERRED_LAYOUTS_VERTICAL = {
default: [ 'v:v' ],
fromGateway: [ 'h:v' ],
toGateway: [ 'v:h' ],
loop: {
fromTop: [ 't:l' ],
fromRight: [ 'r:t' ],
fromLeft: [ 'l:b' ],
fromBottom: [ 'b:r' ]
},
boundaryLoop: {
alternateHorizontalSide: 't',
alternateVerticalSide: 'r',
default: 'h'
},
messageFlow: [ 'straight', 'h:h' ],
subProcess: [ 'straight', 'v:v' ],
isHorizontal: false
};
var oppositeOrientationMapping = {
'top': 'bottom',
'top-right': 'bottom-left',
'top-left': 'bottom-right',
'right': 'left',
'bottom': 'top',
'bottom-right': 'top-left',
'bottom-left': 'top-right',
'left': 'right'
};
var orientationDirectionMapping = {
top: 't',
right: 'r',
bottom: 'b',
left: 'l'
};
export default function BpmnLayouter(elementRegistry) {
this._elementRegistry = elementRegistry;
}
inherits(BpmnLayouter, BaseLayouter);
/**
* Returns waypoints of laid out connection.
*
* @param {Connection} connection
* @param {BpmnLayoutConnectionHints} [hints]
*
* @return {Point[]}
*/
BpmnLayouter.prototype.layoutConnection = function(connection, hints) {
if (!hints) {
hints = {};
}
var source = hints.source || connection.source,
target = hints.target || connection.target,
waypoints = hints.waypoints || connection.waypoints,
connectionStart = hints.connectionStart,
connectionEnd = hints.connectionEnd,
elementRegistry = this._elementRegistry;
var manhattanOptions,
updatedWaypoints;
if (!connectionStart) {
connectionStart = getConnectionDocking(waypoints && waypoints[ 0 ], source);
}
if (!connectionEnd) {
connectionEnd = getConnectionDocking(waypoints && waypoints[ waypoints.length - 1 ], target);
}
if (is(connection, 'bpmn:Association') ||
is(connection, 'bpmn:DataAssociation')) {
if (waypoints && !isCompensationAssociation(source, target)) {
return [].concat([ connectionStart ], waypoints.slice(1, -1), [ connectionEnd ]);
}
}
var layout = isDirectionHorizontal(source, elementRegistry) ? PREFERRED_LAYOUTS_HORIZONTAL : PREFERRED_LAYOUTS_VERTICAL;
if (is(connection, 'bpmn:MessageFlow')) {
manhattanOptions = getMessageFlowManhattanOptions(source, target, layout);
} else if (is(connection, 'bpmn:SequenceFlow') || isCompensationAssociation(source, target)) {
if (source === target) {
manhattanOptions = {
preferredLayouts: getLoopPreferredLayout(source, connection, layout)
};
} else if (is(source, 'bpmn:BoundaryEvent')) {
manhattanOptions = {
preferredLayouts: getBoundaryEventPreferredLayouts(source, target, connectionEnd, layout)
};
} else if (isExpandedSubProcess(source) || isExpandedSubProcess(target)) {
manhattanOptions = {
preferredLayouts: layout.subProcess,
preserveDocking: getSubProcessPreserveDocking(source)
};
} else if (is(source, 'bpmn:Gateway')) {
manhattanOptions = {
preferredLayouts: layout.fromGateway
};
} else if (is(target, 'bpmn:Gateway')) {
manhattanOptions = {
preferredLayouts: layout.toGateway
};
} else {
manhattanOptions = {
preferredLayouts: layout.default
};
}
}
if (manhattanOptions) {
manhattanOptions = assign(manhattanOptions, hints);
updatedWaypoints = withoutRedundantPoints(repairConnection(
source,
target,
connectionStart,
connectionEnd,
waypoints,
manhattanOptions
));
}
return updatedWaypoints || [ connectionStart, connectionEnd ];
};
// helpers //////////
function getAttachOrientation(attachedElement) {
var hostElement = attachedElement.host;
return getOrientation(getMid(attachedElement), hostElement, ATTACH_ORIENTATION_PADDING);
}
function getMessageFlowManhattanOptions(source, target, layout) {
return {
preferredLayouts: layout.messageFlow,
preserveDocking: getMessageFlowPreserveDocking(source, target)
};
}
function getMessageFlowPreserveDocking(source, target) {
// (1) docking element connected to participant has precedence
if (is(target, 'bpmn:Participant')) {
return 'source';
}
if (is(source, 'bpmn:Participant')) {
return 'target';
}
// (2) docking element connected to expanded sub-process has precedence
if (isExpandedSubProcess(target)) {
return 'source';
}
if (isExpandedSubProcess(source)) {
return 'target';
}
// (3) docking event has precedence
if (is(target, 'bpmn:Event')) {
return 'target';
}
if (is(source, 'bpmn:Event')) {
return 'source';
}
return null;
}
function getSubProcessPreserveDocking(source) {
return isExpandedSubProcess(source) ? 'target' : 'source';
}
function getConnectionDocking(point, shape) {
return point ? (point.original || point) : getMid(shape);
}
function isCompensationAssociation(source, target) {
return is(target, 'bpmn:Activity') &&
is(source, 'bpmn:BoundaryEvent') &&
target.businessObject.isForCompensation;
}
function isExpandedSubProcess(element) {
return is(element, 'bpmn:SubProcess') && isExpanded(element);
}
function isSame(a, b) {
return a === b;
}
function isAnyOrientation(orientation, orientations) {
return orientations.indexOf(orientation) !== -1;
}
function getHorizontalOrientation(orientation) {
var matches = /right|left/.exec(orientation);
return matches && matches[0];
}
function getVerticalOrientation(orientation) {
var matches = /top|bottom/.exec(orientation);
return matches && matches[0];
}
function isOppositeOrientation(a, b) {
return oppositeOrientationMapping[a] === b;
}
function isOppositeHorizontalOrientation(a, b) {
var horizontalOrientation = getHorizontalOrientation(a);
var oppositeHorizontalOrientation = oppositeOrientationMapping[horizontalOrientation];
return b.indexOf(oppositeHorizontalOrientation) !== -1;
}
function isOppositeVerticalOrientation(a, b) {
var verticalOrientation = getVerticalOrientation(a);
var oppositeVerticalOrientation = oppositeOrientationMapping[verticalOrientation];
return b.indexOf(oppositeVerticalOrientation) !== -1;
}
function isHorizontalOrientation(orientation) {
return orientation === 'right' || orientation === 'left';
}
function getLoopPreferredLayout(source, connection, layout) {
var waypoints = connection.waypoints;
var orientation = waypoints && waypoints.length && getOrientation(waypoints[0], source);
if (orientation === 'top') {
return layout.loop.fromTop;
} else if (orientation === 'right') {
return layout.loop.fromRight;
} else if (orientation === 'left') {
return layout.loop.fromLeft;
}
return layout.loop.fromBottom;
}
function getBoundaryEventPreferredLayouts(source, target, end, layout) {
var sourceMid = getMid(source),
targetMid = getMid(target),
attachOrientation = getAttachOrientation(source),
sourceLayout,
targetLayout;
var isLoop = isSame(source.host, target);
var attachedToSide = isAnyOrientation(attachOrientation, [ 'top', 'right', 'bottom', 'left' ]);
var targetOrientation = getOrientation(targetMid, sourceMid, {
x: source.width / 2 + target.width / 2,
y: source.height / 2 + target.height / 2
});
if (isLoop) {
return getBoundaryEventLoopLayout(attachOrientation, attachedToSide, source, target, end, layout);
}
// source layout
sourceLayout = getBoundaryEventSourceLayout(attachOrientation, targetOrientation, attachedToSide, layout.isHorizontal);
// target layout
targetLayout = getBoundaryEventTargetLayout(attachOrientation, targetOrientation, attachedToSide, layout.isHorizontal);
return [ sourceLayout + ':' + targetLayout ];
}
function getBoundaryEventLoopLayout(attachOrientation, attachedToSide, source, target, end, layout) {
var orientation = attachedToSide ? attachOrientation : layout.isHorizontal ? getVerticalOrientation(attachOrientation) : getHorizontalOrientation(attachOrientation),
sourceLayout = orientationDirectionMapping[ orientation ],
targetLayout;
if (attachedToSide) {
if (isHorizontalOrientation(attachOrientation)) {
targetLayout = shouldConnectToSameSide('y', source, target, end) ? 'h' : layout.boundaryLoop.alternateHorizontalSide;
} else {
targetLayout = shouldConnectToSameSide('x', source, target, end) ? 'v' : layout.boundaryLoop.alternateVerticalSide;
}
} else {
targetLayout = layout.boundaryLoop.default;
}
return [ sourceLayout + ':' + targetLayout ];
}
function shouldConnectToSameSide(axis, source, target, end) {
var threshold = BOUNDARY_TO_HOST_THRESHOLD;
return !(
areCloseOnAxis(axis, end, target, threshold) ||
areCloseOnAxis(axis, end, {
x: target.x + target.width,
y: target.y + target.height
}, threshold) ||
areCloseOnAxis(axis, end, getMid(source), threshold)
);
}
function areCloseOnAxis(axis, a, b, threshold) {
return Math.abs(a[ axis ] - b[ axis ]) < threshold;
}
function getBoundaryEventSourceLayout(attachOrientation, targetOrientation, attachedToSide, isHorizontal) {
// attached to either top, right, bottom or left side
if (attachedToSide) {
return orientationDirectionMapping[ attachOrientation ];
}
// attached to either top-right, top-left, bottom-right or bottom-left corner
var verticalAttachOrientation = getVerticalOrientation(attachOrientation),
horizontalAttachOrientation = getHorizontalOrientation(attachOrientation),
verticalTargetOrientation = getVerticalOrientation(targetOrientation),
horizontalTargetOrientation = getHorizontalOrientation(targetOrientation);
if (isHorizontal) {
// same vertical or opposite horizontal orientation
if (
isSame(verticalAttachOrientation, verticalTargetOrientation) ||
isOppositeOrientation(horizontalAttachOrientation, horizontalTargetOrientation)
) {
return orientationDirectionMapping[ verticalAttachOrientation ];
}
} else {
// same horizontal or opposite vertical orientation
if (
isSame(horizontalAttachOrientation, horizontalTargetOrientation) ||
isOppositeOrientation(verticalAttachOrientation, verticalTargetOrientation)
) {
return orientationDirectionMapping[ horizontalAttachOrientation ];
}
}
// fallback
return orientationDirectionMapping[ isHorizontal ? horizontalAttachOrientation : verticalAttachOrientation ];
}
function getBoundaryEventTargetLayout(attachOrientation, targetOrientation, attachedToSide, isHorizontal) {
// attached to either top, right, bottom or left side
if (attachedToSide) {
if (isHorizontalOrientation(attachOrientation)) {
// orientation is right or left
// opposite horizontal orientation or same orientation
if (
isOppositeHorizontalOrientation(attachOrientation, targetOrientation) ||
isSame(attachOrientation, targetOrientation)
) {
return 'h';
}
// fallback
return 'v';
} else {
// orientation is top or bottom
// opposite vertical orientation or same orientation
if (
isOppositeVerticalOrientation(attachOrientation, targetOrientation) ||
isSame(attachOrientation, targetOrientation)
) {
return 'v';
}
// fallback
return 'h';
}
}
// attached to either top-right, top-left, bottom-right or bottom-left corner,
// or strictly above/below or left/right of the target. In the corner case,
// the orientation is compared on the counter-axis to decide the layout.
var verticalAttachOrientation = getVerticalOrientation(attachOrientation),
horizontalAttachOrientation = getHorizontalOrientation(attachOrientation),
verticalTargetOrientation = getVerticalOrientation(targetOrientation),
horizontalTargetOrientation = getHorizontalOrientation(targetOrientation);
// If the target is strictly above/below (no horizontal orientation)
if (verticalTargetOrientation && !horizontalTargetOrientation) {
return 'v';
}
// If the target is strictly left/right (no vertical orientation)
if (horizontalTargetOrientation && !verticalTargetOrientation) {
return 'h';
}
if (isHorizontal) {
if (isSame(verticalAttachOrientation, verticalTargetOrientation)) {
return 'h';
} else {
return 'v';
}
} else {
if (isSame(horizontalAttachOrientation, horizontalTargetOrientation)) {
return 'v';
} else {
return 'h';
}
}
}
BpmnLayouter.$inject = [ 'elementRegistry' ];
================================================
FILE: lib/features/modeling/BpmnUpdater.js
================================================
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 {
getBusinessObject,
getDi,
is
} from '../../util/ModelUtil';
import { isAny } from './util/ModelingUtil';
import {
getLabel,
isLabel,
isLabelExternal
} from '../../util/LabelUtil';
import { isPlane } from '../../util/DrilldownUtil';
import { delta } from 'diagram-js/lib/util/PositionUtil';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
/**
* @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
* @typedef {import('./BpmnFactory').default} BpmnFactory
* @typedef {import('diagram-js/lib/layout/CroppingConnectionDocking').default} CroppingConnectionDocking
*
* @typedef {import('../../model/Types').Connection} Connection
* @typedef {import('../../model/Types').Element} Element
* @typedef {import('../../model/Types').Shape} Shape
* @typedef {import('../../model/Types').Parent} Parent
* @typedef {import('../../model/Types').ModdleElement} ModdleElement
*/
/**
* A handler responsible for updating the underlying BPMN 2.0 XML & DI
* once changes on the diagram happen.
*
* @param {EventBus} eventBus
* @param {BpmnFactory} bpmnFactory
* @param {CroppingConnectionDocking} connectionDocking
*/
export default function BpmnUpdater(
eventBus,
bpmnFactory,
connectionDocking
) {
CommandInterceptor.call(this, eventBus);
this._bpmnFactory = bpmnFactory;
var self = this;
// connection cropping //////////////////////
// crop connection ends during create/update
function cropConnection(e) {
var context = e.context,
hints = context.hints || {},
connection;
if (!context.cropped && hints.createElementsBehavior !== false) {
connection = context.connection;
connection.waypoints = connectionDocking.getCroppedWaypoints(connection);
context.cropped = true;
}
}
this.executed([
'connection.layout',
'connection.create'
], cropConnection);
this.reverted([ 'connection.layout' ], function(e) {
delete e.context.cropped;
});
// BPMN + DI update //////////////////////
// update parent
function updateParent(e) {
var context = e.context;
self.updateParent(context.shape || context.connection, context.oldParent);
}
function reverseUpdateParent(e) {
var context = e.context;
var element = context.shape || context.connection,
// oldParent is the (old) new parent, because we are undoing
oldParent = context.parent || context.newParent;
self.updateParent(element, oldParent);
}
this.executed([
'shape.move',
'shape.create',
'shape.delete',
'connection.create',
'connection.move',
'connection.delete'
], ifBpmn(updateParent));
this.reverted([
'shape.move',
'shape.create',
'shape.delete',
'connection.create',
'connection.move',
'connection.delete'
], ifBpmn(reverseUpdateParent));
/*
* ## Updating Parent
*
* When morphing a Process into a Collaboration or vice-versa,
* make sure that both the *semantic* and *di* parent of each element
* is updated.
*
*/
function updateRoot(event) {
var context = event.context,
oldRoot = context.oldRoot,
children = oldRoot.children;
forEach(children, function(child) {
if (is(child, 'bpmn:BaseElement')) {
self.updateParent(child);
}
});
}
this.executed([ 'canvas.updateRoot' ], updateRoot);
this.reverted([ 'canvas.updateRoot' ], updateRoot);
// update bounds
function updateBounds(e) {
var shape = e.context.shape;
if (!is(shape, 'bpmn:BaseElement')) {
return;
}
self.updateBounds(shape);
}
this.executed([ 'shape.move', 'shape.create', 'shape.resize' ], ifBpmn(function(event) {
// exclude labels because they're handled separately during shape.changed
if (event.context.shape.type === 'label') {
return;
}
updateBounds(event);
}));
this.reverted([ 'shape.move', 'shape.create', 'shape.resize' ], ifBpmn(function(event) {
// exclude labels because they're handled separately during shape.changed
if (event.context.shape.type === 'label') {
return;
}
updateBounds(event);
}));
// 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') {
updateBounds({ context: { shape: event.element } });
}
});
// attach / detach connection
function updateConnection(e) {
self.updateConnection(e.context);
}
this.executed([
'connection.create',
'connection.move',
'connection.delete',
'connection.reconnect'
], ifBpmn(updateConnection));
this.reverted([
'connection.create',
'connection.move',
'connection.delete',
'connection.reconnect'
], ifBpmn(updateConnection));
// update waypoints
function updateConnectionWaypoints(e) {
self.updateConnectionWaypoints(e.context.connection);
}
this.executed([
'connection.layout',
'connection.move',
'connection.updateWaypoints',
], ifBpmn(updateConnectionWaypoints));
this.reverted([
'connection.layout',
'connection.move',
'connection.updateWaypoints',
], ifBpmn(updateConnectionWaypoints));
// update conditional/default flows
this.executed('connection.reconnect', ifBpmn(function(event) {
var context = event.context,
connection = context.connection,
oldSource = context.oldSource,
newSource = context.newSource,
connectionBo = getBusinessObject(connection),
oldSourceBo = getBusinessObject(oldSource),
newSourceBo = getBusinessObject(newSource);
// remove condition from connection on reconnect to new source
// if new source can NOT have condional sequence flow
if (connectionBo.conditionExpression && !isAny(newSourceBo, [
'bpmn:Activity',
'bpmn:ExclusiveGateway',
'bpmn:InclusiveGateway'
])) {
context.oldConditionExpression = connectionBo.conditionExpression;
delete connectionBo.conditionExpression;
}
// remove default from old source flow on reconnect to new source
// if source changed
if (oldSource !== newSource && oldSourceBo.default === connectionBo) {
context.oldDefault = oldSourceBo.default;
delete oldSourceBo.default;
}
}));
this.reverted('connection.reconnect', ifBpmn(function(event) {
var context = event.context,
connection = context.connection,
oldSource = context.oldSource,
newSource = context.newSource,
connectionBo = getBusinessObject(connection),
oldSourceBo = getBusinessObject(oldSource),
newSourceBo = getBusinessObject(newSource);
// add condition to connection on revert reconnect to new source
if (context.oldConditionExpression) {
connectionBo.conditionExpression = context.oldConditionExpression;
}
// add default to old source on revert reconnect to new source
if (context.oldDefault) {
oldSourceBo.default = context.oldDefault;
delete newSourceBo.default;
}
}));
// update attachments
function updateAttachment(e) {
self.updateAttachment(e.context);
}
this.executed([ 'element.updateAttachment' ], ifBpmn(updateAttachment));
this.reverted([ 'element.updateAttachment' ], ifBpmn(updateAttachment));
// update BPMNLabel
this.executed('element.updateLabel', ifBpmn(updateBPMNLabel));
this.reverted('element.updateLabel', ifBpmn(updateBPMNLabel));
function updateBPMNLabel(event) {
const { element } = event.context,
label = getLabel(element);
const di = getDi(element),
diLabel = di && di.get('label');
if (isLabelExternal(element) || isPlane(element)) {
return;
}
if (label && !diLabel) {
di.set('label', bpmnFactory.create('bpmndi:BPMNLabel'));
} else if (!label && diLabel) {
di.set('label', undefined);
}
}
}
inherits(BpmnUpdater, CommandInterceptor);
BpmnUpdater.$inject = [
'eventBus',
'bpmnFactory',
'connectionDocking'
];
// implementation //////////////////////
/**
* @param { {
* shape: Shape;
* host: Shape;
* } } context
*/
BpmnUpdater.prototype.updateAttachment = function(context) {
var shape = context.shape,
businessObject = shape.businessObject,
host = shape.host;
businessObject.attachedToRef = host && host.businessObject;
};
/**
* @param {Element} element
* @param {Parent} oldParent
*/
BpmnUpdater.prototype.updateParent = function(element, oldParent) {
// do not update BPMN 2.0 label parent
if (isLabel(element)) {
return;
}
// data stores in collaborations are handled separately by DataStoreBehavior
if (is(element, 'bpmn:DataStoreReference') &&
element.parent &&
is(element.parent, 'bpmn:Collaboration')) {
return;
}
var parentShape = element.parent;
var businessObject = element.businessObject,
di = getDi(element),
parentBusinessObject = parentShape && parentShape.businessObject,
parentDi = getDi(parentShape);
if (is(element, 'bpmn:FlowNode')) {
this.updateFlowNodeRefs(businessObject, parentBusinessObject, oldParent && oldParent.businessObject);
}
if (is(element, 'bpmn:DataOutputAssociation')) {
if (element.source) {
parentBusinessObject = element.source.businessObject;
} else {
parentBusinessObject = null;
}
}
if (is(element, 'bpmn:DataInputAssociation')) {
if (element.target) {
parentBusinessObject = element.target.businessObject;
} else {
parentBusinessObject = null;
}
}
this.updateSemanticParent(businessObject, parentBusinessObject);
if (is(element, 'bpmn:DataObjectReference') && businessObject.dataObjectRef) {
this.updateSemanticParent(businessObject.dataObjectRef, parentBusinessObject);
}
this.updateDiParent(di, parentDi);
};
/**
* @param {Shape} shape
*/
BpmnUpdater.prototype.updateBounds = function(shape) {
var di = getDi(shape),
embeddedLabelBounds = getEmbeddedLabelBounds(shape);
// update embedded label bounds if possible
if (embeddedLabelBounds) {
var embeddedLabelBoundsDelta = delta(embeddedLabelBounds, di.get('bounds'));
assign(embeddedLabelBounds, {
x: shape.x + embeddedLabelBoundsDelta.x,
y: shape.y + embeddedLabelBoundsDelta.y
});
}
var target = isLabel(shape) ? this._getLabel(di) : di;
var bounds = target.bounds;
if (!bounds) {
bounds = this._bpmnFactory.createDiBounds();
target.set('bounds', bounds);
}
assign(bounds, {
x: shape.x,
y: shape.y,
width: shape.width,
height: shape.height
});
};
/**
* @param {ModdleElement} businessObject
* @param {ModdleElement} newContainment
* @param {ModdleElement} oldContainment
*/
BpmnUpdater.prototype.updateFlowNodeRefs = function(businessObject, newContainment, oldContainment) {
if (oldContainment === newContainment) {
return;
}
var oldRefs, newRefs;
if (is (oldContainment, 'bpmn:Lane')) {
oldRefs = oldContainment.get('flowNodeRef');
collectionRemove(oldRefs, businessObject);
}
if (is(newContainment, 'bpmn:Lane')) {
newRefs = newContainment.get('flowNodeRef');
collectionAdd(newRefs, businessObject);
}
};
/**
* @param {Connection} connection
* @param {Element} newSource
* @param {Element} newTarget
*/
BpmnUpdater.prototype.updateDiConnection = function(connection, newSource, newTarget) {
var connectionDi = getDi(connection),
newSourceDi = getDi(newSource),
newTargetDi = getDi(newTarget);
if (connectionDi.sourceElement && connectionDi.sourceElement.bpmnElement !== getBusinessObject(newSource)) {
connectionDi.sourceElement = newSource && newSourceDi;
}
if (connectionDi.targetElement && connectionDi.targetElement.bpmnElement !== getBusinessObject(newTarget)) {
connectionDi.targetElement = newTarget && newTargetDi;
}
};
/**
* @param {ModdleElement} di
* @param {ModdleElement} parentDi
*/
BpmnUpdater.prototype.updateDiParent = function(di, parentDi) {
if (parentDi && !is(parentDi, 'bpmndi:BPMNPlane')) {
parentDi = parentDi.$parent;
}
if (di.$parent === parentDi) {
return;
}
var planeElements = (parentDi || di.$parent).get('planeElement');
if (parentDi) {
planeElements.push(di);
di.$parent = parentDi;
} else {
collectionRemove(planeElements, di);
di.$parent = null;
}
};
/**
* @param {ModdleElement} element
*
* @return {ModdleElement}
*/
function getDefinitions(element) {
while (element && !is(element, 'bpmn:Definitions')) {
element = element.$parent;
}
return element;
}
/**
* @param {ModdleElement} container
*
* @return {ModdleElement}
*/
BpmnUpdater.prototype.getLaneSet = function(container) {
var laneSet, laneSets;
// bpmn:Lane
if (is(container, 'bpmn:Lane')) {
laneSet = container.childLaneSet;
if (!laneSet) {
laneSet = this._bpmnFactory.create('bpmn:LaneSet');
container.childLaneSet = laneSet;
laneSet.$parent = container;
}
return laneSet;
}
// bpmn:Participant
if (is(container, 'bpmn:Participant')) {
container = container.processRef;
}
// bpmn:FlowElementsContainer
laneSets = container.get('laneSets');
laneSet = laneSets[0];
if (!laneSet) {
laneSet = this._bpmnFactory.create('bpmn:LaneSet');
laneSet.$parent = container;
laneSets.push(laneSet);
}
return laneSet;
};
/**
* @param {ModdleElement} businessObject
* @param {ModdleElement} newParent
* @param {ModdleElement} visualParent
*/
BpmnUpdater.prototype.updateSemanticParent = function(businessObject, newParent, visualParent) {
var containment;
if (businessObject.$parent === newParent) {
return;
}
if (is(businessObject, 'bpmn:DataInput') || is(businessObject, 'bpmn:DataOutput')) {
if (is(newParent, 'bpmn:Participant') && 'processRef' in newParent) {
newParent = newParent.processRef;
}
// already in correct ioSpecification
if ('ioSpecification' in newParent && newParent.ioSpecification === businessObject.$parent) {
return;
}
}
if (is(businessObject, 'bpmn:Lane')) {
if (newParent) {
newParent = this.getLaneSet(newParent);
}
containment = 'lanes';
} else if (is(businessObject, 'bpmn:FlowElement')) {
if (newParent) {
if (is(newParent, 'bpmn:Participant')) {
newParent = newParent.processRef;
} else if (is(newParent, 'bpmn:Lane')) {
do {
// unwrap Lane -> LaneSet -> (Lane | FlowElementsContainer)
newParent = newParent.$parent.$parent;
} while (is(newParent, 'bpmn:Lane'));
}
}
containment = 'flowElements';
} else if (is(businessObject, 'bpmn:Artifact')) {
while (newParent &&
!is(newParent, 'bpmn:Process') &&
!is(newParent, 'bpmn:SubProcess') &&
!is(newParent, 'bpmn:Collaboration')) {
if (is(newParent, 'bpmn:Participant')) {
newParent = newParent.processRef;
break;
} else {
newParent = newParent.$parent;
}
}
containment = 'artifacts';
} else if (is(businessObject, 'bpmn:MessageFlow')) {
containment = 'messageFlows';
} else if (is(businessObject, 'bpmn:Participant')) {
containment = 'participants';
// make sure the participants process is properly attached / detached
// from the XML document
var process = businessObject.processRef,
definitions;
if (process) {
definitions = getDefinitions(businessObject.$parent || newParent);
if (businessObject.$parent) {
collectionRemove(definitions.get('rootElements'), process);
process.$parent = null;
}
if (newParent) {
collectionAdd(definitions.get('rootElements'), process);
process.$parent = definitions;
}
}
} else if (is(businessObject, 'bpmn:DataOutputAssociation')) {
containment = 'dataOutputAssociations';
} else if (is(businessObject, 'bpmn:DataInputAssociation')) {
containment = 'dataInputAssociations';
}
if (!containment) {
throw new Error(`no parent for <${ businessObject.id }> in <${ newParent.id }>`);
}
var children;
if (businessObject.$parent) {
// remove from old parent
children = businessObject.$parent.get(containment);
collectionRemove(children, businessObject);
}
if (!newParent) {
businessObject.$parent = null;
} else {
// add to new parent
children = newParent.get(containment);
children.push(businessObject);
businessObject.$parent = newParent;
}
if (visualParent) {
var diChildren = visualParent.get(containment);
collectionRemove(children, businessObject);
if (newParent) {
if (!diChildren) {
diChildren = [];
newParent.set(containment, diChildren);
}
diChildren.push(businessObject);
}
}
};
/**
* @param {Connection} connection
*/
BpmnUpdater.prototype.updateConnectionWaypoints = function(connection) {
var di = getDi(connection);
di.set('waypoint', this._bpmnFactory.createDiWaypoints(connection.waypoints));
};
/**
* @param { {
* connection: Connection;
* parent: Parent;
* newParent: Parent;
* } } context
*/
BpmnUpdater.prototype.updateConnection = function(context) {
var connection = context.connection,
businessObject = getBusinessObject(connection),
newSource = connection.source,
newSourceBo = getBusinessObject(newSource),
newTarget = connection.target,
newTargetBo = getBusinessObject(connection.target),
visualParent;
if (!is(businessObject, 'bpmn:DataAssociation')) {
var inverseSet = is(businessObject, 'bpmn:SequenceFlow');
if (businessObject.sourceRef !== newSourceBo) {
if (inverseSet) {
collectionRemove(businessObject.sourceRef && businessObject.sourceRef.get('outgoing'), businessObject);
if (newSourceBo && newSourceBo.get('outgoing')) {
newSourceBo.get('outgoing').push(businessObject);
}
}
businessObject.sourceRef = newSourceBo;
}
if (businessObject.targetRef !== newTargetBo) {
if (inverseSet) {
collectionRemove(businessObject.targetRef && businessObject.targetRef.get('incoming'), businessObject);
if (newTargetBo && newTargetBo.get('incoming')) {
newTargetBo.get('incoming').push(businessObject);
}
}
businessObject.targetRef = newTargetBo;
}
} else if (is(businessObject, 'bpmn:DataInputAssociation')) {
// handle obnoxious isMsome sourceRef
businessObject.get('sourceRef')[0] = newSourceBo;
visualParent = context.parent || context.newParent || newTargetBo;
this.updateSemanticParent(businessObject, newTargetBo, visualParent);
} else if (is(businessObject, 'bpmn:DataOutputAssociation')) {
visualParent = context.parent || context.newParent || newSourceBo;
this.updateSemanticParent(businessObject, newSourceBo, visualParent);
// targetRef = new target
businessObject.targetRef = newTargetBo;
}
this.updateConnectionWaypoints(connection);
this.updateDiConnection(connection, newSource, newTarget);
};
// helpers //////////////////////
BpmnUpdater.prototype._getLabel = function(di) {
if (!di.label) {
di.label = this._bpmnFactory.createDiLabel();
}
return di.label;
};
/**
* Call function if shape or connection is BPMN element.
*
* @param {Function} fn
*
* @return {Function}
*/
function ifBpmn(fn) {
return function(event) {
var context = event.context,
element = context.shape || context.connection || context.element;
if (is(element, 'bpmn:BaseElement')) {
fn(event);
}
};
}
/**
* Return dc:Bounds of bpmndi:BPMNLabel if exists.
*
* @param {Shape} shape
*
* @return {ModdleElement|undefined}
*/
function getEmbeddedLabelBounds(shape) {
if (!is(shape, 'bpmn:Activity')) {
return;
}
var di = getDi(shape);
if (!di) {
return;
}
var label = di.get('label');
if (!label) {
return;
}
return label.get('bounds');
}
================================================
FILE: lib/features/modeling/ElementFactory.js
================================================
import {
assign,
forEach,
has,
isDefined,
isObject,
omit
} from 'min-dash';
import inherits from 'inherits-browser';
import {
getBusinessObject,
getDi,
is
} from '../../util/ModelUtil';
import {
isAny
} from '../modeling/util/ModelingUtil';
import {
isExpanded
} from '../../util/DiUtil';
import BaseElementFactory from 'diagram-js/lib/core/ElementFactory';
import {
DEFAULT_LABEL_SIZE
} from '../../util/LabelUtil';
import {
ensureCompatDiRef
} from '../../util/CompatibilityUtil';
/**
* @typedef {import('diagram-js/lib/util/Types').Dimensions} Dimensions
*
* @typedef {import('./BpmnFactory').default} BpmnFactory
*
* @typedef {import('../../model/Types').BpmnAttributes} BpmnAttributes
* @typedef {import('../../model/Types').Connection} Connection
* @typedef {import('../../model/Types').Element} Element
* @typedef {import('../../model/Types').Label} Label
* @typedef {import('../../model/Types').Root} Root
* @typedef {import('../../model/Types').Shape} Shape
* @typedef {import('../../model/Types').Moddle} Moddle
* @typedef {import('../../model/Types').ModdleElement} ModdleElement
*/
/**
* A BPMN-specific element factory.
*
* @template {Connection} [T=Connection]
* @template {Label} [U=Label]
* @template {Root} [V=Root]
* @template {Shape} [W=Shape]
*
* @extends {BaseElementFactory}
*
* @param {BpmnFactory} bpmnFactory
* @param {Moddle} moddle
*/
export default function ElementFactory(bpmnFactory, moddle) {
BaseElementFactory.call(this);
this._bpmnFactory = bpmnFactory;
this._moddle = moddle;
}
inherits(ElementFactory, BaseElementFactory);
ElementFactory.$inject = [
'bpmnFactory',
'moddle'
];
ElementFactory.prototype._baseCreate = BaseElementFactory.prototype.create;
/**
* Create a root element.
*
* @overlord
* @param {'root'} elementType
* @param {Partial & Partial} [attrs]
* @return {V}
*/
/**
* Create a shape.
*
* @overlord
* @param {'shape'} elementType
* @param {Partial & Partial} [attrs]
* @return {W}
*/
/**
* Create a connection.
*
* @overlord
* @param {'connection'} elementType
* @param {Partial & Partial} [attrs]
* @return {T}
*/
/**
* Create a label.
*
* @param {'label'} elementType
* @param {Partial & Partial} [attrs]
* @return {U}
*/
ElementFactory.prototype.create = function(elementType, attrs) {
// no special magic for labels,
// we assume their businessObjects have already been created
// and wired via attrs
if (elementType === 'label') {
var di = attrs.di || this._bpmnFactory.createDiLabel();
return this._baseCreate(elementType, assign({ type: 'label', di: di }, DEFAULT_LABEL_SIZE, attrs));
}
return this.createElement(elementType, attrs);
};
/**
* Create a BPMN root element.
*
* @overlord
* @param {'root'} elementType
* @param {Partial & Partial} [attrs]
* @return {V}
*/
/**
* Create a BPMN shape.
*
* @overlord
* @param {'shape'} elementType
* @param {Partial & Partial} [attrs]
* @return {W}
*/
/**
* Create a BPMN connection.
*
* @param {'connection'} elementType
* @param {Partial & Partial} [attrs]
* @return {T}
*/
ElementFactory.prototype.createElement = function(elementType, attrs) {
attrs = assign({}, attrs || {});
var size;
var businessObject = attrs.businessObject,
di = attrs.di;
if (!businessObject) {
if (!attrs.type) {
throw new Error('no shape type specified');
}
businessObject = this._bpmnFactory.create(attrs.type);
ensureCompatDiRef(businessObject);
}
if (!isModdleDi(di)) {
var diAttrs = assign(
{},
di || {},
{ id: businessObject.id + '_di' }
);
if (elementType === 'root') {
di = this._bpmnFactory.createDiPlane(businessObject, diAttrs);
} else if (elementType === 'connection') {
di = this._bpmnFactory.createDiEdge(businessObject, diAttrs);
} else {
di = this._bpmnFactory.createDiShape(businessObject, diAttrs);
}
}
if (is(businessObject, 'bpmn:Group')) {
attrs = assign({
isFrame: true
}, attrs);
}
attrs = applyAttributes(businessObject, attrs, [
'processRef',
'isInterrupting',
'associationDirection',
'isForCompensation'
]);
if (attrs.isExpanded) {
attrs = applyAttribute(di, attrs, 'isExpanded');
}
if (isAny(businessObject, [ 'bpmn:Lane', 'bpmn:Participant' ])) {
attrs = applyAttribute(di, attrs, 'isHorizontal');
}
if (is(businessObject, 'bpmn:SubProcess')) {
attrs.collapsed = !isExpanded(businessObject, di);
}
if (is(businessObject, 'bpmn:ExclusiveGateway')) {
if (has(di, 'isMarkerVisible')) {
if (di.isMarkerVisible === undefined) {
di.isMarkerVisible = false;
}
} else {
di.isMarkerVisible = true;
}
}
if (isDefined(attrs.triggeredByEvent)) {
businessObject.triggeredByEvent = attrs.triggeredByEvent;
delete attrs.triggeredByEvent;
}
if (isDefined(attrs.cancelActivity)) {
businessObject.cancelActivity = attrs.cancelActivity;
delete attrs.cancelActivity;
}
var eventDefinitions,
newEventDefinition;
if (attrs.eventDefinitionType) {
eventDefinitions = businessObject.get('eventDefinitions') || [];
newEventDefinition = this._bpmnFactory.create(attrs.eventDefinitionType, attrs.eventDefinitionAttrs);
if (attrs.eventDefinitionType === 'bpmn:ConditionalEventDefinition') {
newEventDefinition.condition = this._bpmnFactory.create('bpmn:FormalExpression');
}
eventDefinitions.push(newEventDefinition);
newEventDefinition.$parent = businessObject;
businessObject.eventDefinitions = eventDefinitions;
delete attrs.eventDefinitionType;
}
size = this.getDefaultSize(businessObject, di);
attrs = assign({
id: businessObject.id
}, size, attrs, {
businessObject: businessObject,
di: di
});
return this._baseCreate(elementType, attrs);
};
/**
* Get the default size of a diagram element.
*
* @param {Element} element The element.
* @param {ModdleElement} di The DI.
*
* @return {Dimensions} Default width and height of the element.
*/
ElementFactory.prototype.getDefaultSize = function(element, di) {
var bo = getBusinessObject(element);
di = di || getDi(element);
if (is(bo, 'bpmn:SubProcess')) {
if (isExpanded(bo, di)) {
return { width: 350, height: 200 };
} else {
return { width: 100, height: 80 };
}
}
if (is(bo, 'bpmn:Task')) {
return { width: 100, height: 80 };
}
if (is(bo, 'bpmn:Gateway')) {
return { width: 50, height: 50 };
}
if (is(bo, 'bpmn:Event')) {
return { width: 36, height: 36 };
}
if (is(bo, 'bpmn:Participant')) {
var isHorizontalPool = di.isHorizontal === undefined || di.isHorizontal === true;
if (isExpanded(bo, di)) {
if (isHorizontalPool) {
return { width: 600, height: 250 };
}
return { width: 250, height: 600 };
} else {
if (isHorizontalPool) {
return { width: 400, height: 60 };
}
return { width: 60, height: 400 };
}
}
if (is(bo, 'bpmn:Lane')) {
return { width: 400, height: 100 };
}
if (is(bo, 'bpmn:DataObjectReference')) {
return { width: 36, height: 50 };
}
if (is(bo, 'bpmn:DataStoreReference')) {
return { width: 50, height: 50 };
}
if (is(bo, 'bpmn:TextAnnotation')) {
return { width: 100, height: 30 };
}
if (is(bo, 'bpmn:Group')) {
return { width: 300, height: 300 };
}
return { width: 100, height: 80 };
};
/**
* Create participant.
*
* @param {boolean|Partial & Partial} [attrs]
* Attributes or whether the participant is expanded.
*
* @return {W} The created participant.
*/
ElementFactory.prototype.createParticipantShape = function(attrs) {
if (!isObject(attrs)) {
attrs = { isExpanded: attrs };
}
attrs = assign({ type: 'bpmn:Participant' }, attrs || {});
// participants are expanded by default
if (attrs.isExpanded !== false) {
attrs.processRef = this._bpmnFactory.create('bpmn:Process');
}
return this.createShape(attrs);
};
// helpers //////////////////////
/**
* Apply attributes from a map to the given element, remove attribute from the
* map on application.
*
* @param {Element} element
* @param {Object} attrs (in/out map of attributes)
* @param {string[]} attributeNames name of attributes to apply
*
* @return {Object} changed attrs
*/
function applyAttributes(element, attrs, attributeNames) {
forEach(attributeNames, function(property) {
attrs = applyAttribute(element, attrs, property);
});
return attrs;
}
/**
* Apply named property to element and drain it from the attrs collection.
*
* @param {Element} element
* @param {Object} attrs (in/out map of attributes)
* @param {string} attributeName to apply
*
* @return {Object} changed attrs
*/
function applyAttribute(element, attrs, attributeName) {
if (attrs[attributeName] === undefined) {
return attrs;
}
element[attributeName] = attrs[attributeName];
return omit(attrs, [ attributeName ]);
}
/**
* @param {Element} element
*
* @return {boolean}
*/
function isModdleDi(element) {
return isAny(element, [
'bpmndi:BPMNShape',
'bpmndi:BPMNEdge',
'bpmndi:BPMNDiagram',
'bpmndi:BPMNPlane',
]);
}
================================================
FILE: lib/features/modeling/ElementFactory.test.ts
================================================
import Modeler from '../../Modeler';
import ElementFactory from './ElementFactory';
import {
Connection,
Label,
Root,
Shape
} from '../../model/Types';
const modeler = new Modeler();
const elementFactory = modeler.get('elementFactory');
const shape1 = elementFactory.create('shape', {
type: 'bpmn:Task',
id: 'shape1',
x: 100,
y: 100,
width: 100,
height: 100
});
const shape2 = elementFactory.create('shape', {
type: 'bpmn:Task',
id: 'shape2',
x: 100,
y: 100,
width: 100,
height: 100
});
const connection = elementFactory.create('connection', {
type: 'bpmn:SequenceFlow',
id: 'connection',
source: shape1,
target: shape2,
waypoints: []
});
elementFactory.create('root', {
type: 'bpmn:Process',
id: 'root'
});
elementFactory.create('label', {
type: 'bpmn:Task',
id: 'label'
});
elementFactory.create('shape', {
type: 'bpmn:Task'
});
elementFactory.create('connection', {
type: 'bpmn:SequenceFlow'
});
elementFactory.create('root', {
type: 'bpmn:Process'
});
elementFactory.create('label', {
type: 'bpmn:Task'
});
elementFactory.create('connection', {
type: 'bpmn:Association',
associationDirection: 'One'
});
elementFactory.create('shape', {
type: 'bpmn:BoundaryEvent',
cancelActivity: true,
eventDefinitionType: 'bpmn:ErrorEventDefinition'
});
elementFactory.create('shape', {
type: 'bpmn:Task',
isForCompensation: false
});
elementFactory.create('shape', {
type: 'bpmn:Participant',
processRef: {}
});
elementFactory.create('shape', {
type: 'bpmn:SubProcess',
triggeredByEvent: true
});
elementFactory.createElement('connection', {
type: 'bpmn:SequenceFlow'
});
elementFactory.createElement('root', {
type: 'bpmn:Process'
});
elementFactory.createElement('shape', {
type: 'bpmn:Task'
});
elementFactory.getDefaultSize(shape1, { type: 'bpmndi:BPMNShape' });
elementFactory.getDefaultSize(connection, { type: 'bpmndi:BPMNEdge' });
elementFactory.createParticipantShape();
elementFactory.createParticipantShape(true);
elementFactory.createParticipantShape({
type: 'bpmn:Participant',
isExpanded: true
});
/**
* Customization
*/
type CustomShape = {
foo: string;
} & Shape;
export class CustomElementFactory extends ElementFactory {};
const customElementFactory = modeler.get('elementFactory');
const customShape = customElementFactory.createShape({ foo: 'bar' });
console.log(customShape.foo);
================================================
FILE: lib/features/modeling/Modeling.js
================================================
import inherits from 'inherits-browser';
import BaseModeling from 'diagram-js/lib/features/modeling/Modeling';
import UpdateModdlePropertiesHandler from './cmd/UpdateModdlePropertiesHandler';
import UpdatePropertiesHandler from './cmd/UpdatePropertiesHandler';
import UpdateCanvasRootHandler from './cmd/UpdateCanvasRootHandler';
import AddLaneHandler from './cmd/AddLaneHandler';
import SplitLaneHandler from './cmd/SplitLaneHandler';
import ResizeLaneHandler from './cmd/ResizeLaneHandler';
import UpdateFlowNodeRefsHandler from './cmd/UpdateFlowNodeRefsHandler';
import IdClaimHandler from './cmd/IdClaimHandler';
import SetColorHandler from './cmd/SetColorHandler';
import UpdateLabelHandler from '../label-editing/cmd/UpdateLabelHandler';
/**
* @typedef {import('../rules/BpmnRules').default} BpmnRules
* @typedef {import('diagram-js/lib/command/CommandStack').default} CommandStack
* @typedef {import('./ElementFactory').default} ElementFactory
* @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
*
* @typedef {import('diagram-js/lib/features/modeling/Modeling').ModelingHints} ModelingHints
*
* @typedef {import('../../model/Types').Connection} Connection
* @typedef {import('../../model/Types').Element} Element
* @typedef {import('../../model/Types').Label} Label
* @typedef {import('../../model/Types').Parent} Parent
* @typedef {import('../../model/Types').Root} Root
* @typedef {import('../../model/Types').Shape} Shape
* @typedef {import('../../model/Types').ModdleElement} ModdleElement
*
* @typedef {import('diagram-js/lib/util/Types').Rect} Rect
*
* @typedef {import('../../util/Types').Colors} Colors
*
* @typedef { {
* removeShape?: boolean;
* } } UpdateLabelHints
*/
/**
* The BPMN 2.0 modeling entry point.
*
* @template {Connection} [T=Connection]
* @template {Element} [U=Element]
* @template {Label} [V=Label]
* @template {Parent} [W=Parent]
* @template {Shape} [X=Shape]
*
* @extends {BaseModeling}
*
* @param {EventBus} eventBus
* @param {ElementFactory} elementFactory
* @param {CommandStack} commandStack
* @param {BpmnRules} bpmnRules
*/
export default function Modeling(
eventBus,
elementFactory,
commandStack,
bpmnRules
) {
BaseModeling.call(this, eventBus, elementFactory, commandStack);
this._bpmnRules = bpmnRules;
}
inherits(Modeling, BaseModeling);
Modeling.$inject = [
'eventBus',
'elementFactory',
'commandStack',
'bpmnRules'
];
Modeling.prototype.getHandlers = function() {
var handlers = BaseModeling.prototype.getHandlers.call(this);
handlers['element.updateModdleProperties'] = UpdateModdlePropertiesHandler;
handlers['element.updateProperties'] = UpdatePropertiesHandler;
handlers['canvas.updateRoot'] = UpdateCanvasRootHandler;
handlers['lane.add'] = AddLaneHandler;
handlers['lane.resize'] = ResizeLaneHandler;
handlers['lane.split'] = SplitLaneHandler;
handlers['lane.updateRefs'] = UpdateFlowNodeRefsHandler;
handlers['id.updateClaim'] = IdClaimHandler;
handlers['element.setColor'] = SetColorHandler;
handlers['element.updateLabel'] = UpdateLabelHandler;
return handlers;
};
/**
* Update an element's label.
*
* @param {Element} element The element.
* @param {string} newLabel The new label.
* @param {Rect} [newBounds] The optional bounds of the label.
* @param {UpdateLabelHints} [hints] The optional hints.
*/
Modeling.prototype.updateLabel = function(element, newLabel, newBounds, hints) {
this._commandStack.execute('element.updateLabel', {
element: element,
newLabel: newLabel,
newBounds: newBounds,
hints: hints || {}
});
};
/**
* @param {Element} source
* @param {Element} target
* @param {Partial} [attrs]
* @param {ModelingHints} [hints]
*
* @return {T}
*/
Modeling.prototype.connect = function(source, target, attrs, hints) {
var bpmnRules = this._bpmnRules;
if (!attrs) {
attrs = bpmnRules.canConnect(source, target);
}
if (!attrs) {
return;
}
return this.createConnection(source, target, attrs, source.parent, hints);
};
/**
* Update a model element's properties.
*
* @param {Element} element The element.
* @param {ModdleElement} moddleElement The model element.
* @param {Object} properties The updated properties.
*/
Modeling.prototype.updateModdleProperties = function(element, moddleElement, properties) {
this._commandStack.execute('element.updateModdleProperties', {
element: element,
moddleElement: moddleElement,
properties: properties
});
};
/**
* Update an element's properties.
*
* @param {Element} element The element.
* @param {Object} properties The updated properties.
*/
Modeling.prototype.updateProperties = function(element, properties) {
this._commandStack.execute('element.updateProperties', {
element: element,
properties: properties
});
};
/**
* Resize a lane.
*
* @param {Shape} laneShape The lane.
* @param {Rect} newBounds The new bounds of the lane.
* @param {boolean} [balanced] Wether to resize neighboring lanes.
*/
Modeling.prototype.resizeLane = function(laneShape, newBounds, balanced) {
this._commandStack.execute('lane.resize', {
shape: laneShape,
newBounds: newBounds,
balanced: balanced
});
};
/**
* Add a lane.
*
* @param {Shape} targetLaneShape The shape to add the lane to.
* @param {string} location The location.
*
* @return {Shape} The added lane.
*/
Modeling.prototype.addLane = function(targetLaneShape, location) {
var context = {
shape: targetLaneShape,
location: location
};
this._commandStack.execute('lane.add', context);
return context.newLane;
};
/**
* Split a lane.
*
* @param {Shape} targetLane The lane to split.
* @param {number} count The number of lanes to split the lane into. Must not
* exceed the number of existing lanes.
*/
Modeling.prototype.splitLane = function(targetLane, count) {
this._commandStack.execute('lane.split', {
shape: targetLane,
count: count
});
};
/**
* Turn a process into a collaboration.
*
* @return {Root} The root of the collaboration.
*/
Modeling.prototype.makeCollaboration = function() {
var collaborationElement = this._create('root', {
type: 'bpmn:Collaboration'
});
var context = {
newRoot: collaborationElement
};
this._commandStack.execute('canvas.updateRoot', context);
return collaborationElement;
};
/**
* Transform a collaboration into a process.
*
* @return {Root} The root of the process.
*/
Modeling.prototype.makeProcess = function() {
var processElement = this._create('root', {
type: 'bpmn:Process'
});
var context = {
newRoot: processElement
};
this._commandStack.execute('canvas.updateRoot', context);
return processElement;
};
/**
* Update the referenced lanes of each flow node.
*
* @param {Shape[]} flowNodeShapes The flow nodes to update.
* @param {Shape[]} laneShapes The lanes.
*/
Modeling.prototype.updateLaneRefs = function(flowNodeShapes, laneShapes) {
this._commandStack.execute('lane.updateRefs', {
flowNodeShapes: flowNodeShapes,
laneShapes: laneShapes
});
};
/**
* Claim an ID.
*
* @param {string} id The ID to claim.
* @param {ModdleElement} moddleElement The model element the ID is claimed for.
*/
Modeling.prototype.claimId = function(id, moddleElement) {
this._commandStack.execute('id.updateClaim', {
id: id,
element: moddleElement,
claiming: true
});
};
/**
* Unclaim an ID.
*
* @param {string} id The ID to unclaim.
* @param {ModdleElement} moddleElement The model element the ID is claimed for.
*/
Modeling.prototype.unclaimId = function(id, moddleElement) {
this._commandStack.execute('id.updateClaim', {
id: id,
element: moddleElement
});
};
/**
* Set the color(s) of one or many elements.
*
* @param {Element[]} elements The elements to set the color(s) for.
* @param {Colors} colors The color(s) to set.
*/
Modeling.prototype.setColor = function(elements, colors) {
if (!elements.length) {
elements = [ elements ];
}
this._commandStack.execute('element.setColor', {
elements: elements,
colors: colors
});
};
================================================
FILE: lib/features/modeling/Modeling.test.ts
================================================
import { expectType } from 'ts-expect';
import Modeler from '../../Modeler';
import {
Connection,
Element,
Label,
Parent,
Shape
} from '../../model/Types';
import ElementFactory from './ElementFactory';
import Modeling from './Modeling';
import { getBusinessObject } from '../../util/ModelUtil';
import { CustomElementFactory } from './ElementFactory.test';
const modeler = new Modeler();
const elementFactory = modeler.get('elementFactory');
const sequenceFlow = elementFactory.create('connection', { type: 'bpmn:SequenceFlow' }),
bpmnProcess = elementFactory.create('root', { type: 'bpmn:Process' }),
subProcess = elementFactory.create('shape', { type: 'bpmn:SubProcess' }),
task = elementFactory.create('shape', { type: 'bpmn:Task' });
const modeling = modeler.get('modeling');
modeling.updateLabel(task, 'foo');
modeling.updateLabel(task, 'foo', {
x: 100,
y: 100,
width: 100,
height: 100
});
modeling.updateLabel(task, 'foo', {
x: 100,
y: 100,
width: 100,
height: 100
}, { removeShape: true });
modeling.connect(subProcess, task, sequenceFlow);
modeling.connect(subProcess, task, sequenceFlow, { foo: 'bar' });
modeling.updateModdleProperties(task, { type: 'bpmn:ExtensionElements' }, {
values: []
});
modeling.updateProperties(task, {
name: 'foo'
});
const participant = elementFactory.create('shape', { type: 'bpmn:Participant'}),
lane = elementFactory.create('shape', { type: 'bpmn:Lane'});
modeling.resizeLane(lane, {
x: 100,
y: 100,
width: 100,
height: 100
});
modeling.resizeLane(lane, {
x: 100,
y: 100,
width: 100,
height: 100
}, true);
modeling.addLane(participant, 'top');
modeling.addLane(participant, 'bottom');
modeling.splitLane(lane, 3);
modeling.makeCollaboration();
modeling.makeProcess();
modeling.updateLaneRefs([ task ], [ lane ]);
modeling.claimId('foo', task.businessObject);
modeling.unclaimId('foo', task.businessObject);
modeling.setColor([ task ], { fill: 'red', stroke: 'green' });
modeling.setColor([ task ], { fill: 'red' });
modeling.setColor([ task ], { stroke: 'green' });
/**
* Integration
*/
expectType(modeling.createConnection(subProcess, task, sequenceFlow, bpmnProcess));
expectType(modeling.createLabel(task, { x: 100, y: 100 }, {
businessObject: getBusinessObject(task)
}));
expectType(modeling.createShape(task, { x: 100, y: 100 }, bpmnProcess));
expectType(modeling.createElements([
subProcess,
task,
sequenceFlow
], { x: 100, y: 100 }, bpmnProcess));
modeling.moveShape(task, { x: 100, y: 100 });
modeling.moveConnection(sequenceFlow, { x: 100, y: 100 });
modeling.moveElements([ subProcess, task ], { x: 100, y: 100 });
/**
* Customization
*/
type CustomElement = {
foo: string;
} & Element;
type CustomShape = {
bar: string;
} & Shape & CustomElement;
class CustomModeling extends Modeling {};
const customModeling = modeler.get('modeling');
const customShape = customModeling.createShape({ bar: 'bar' }, { x: 100, y: 100 }, modeler.get('elementFactory').create('root'));
customModeling.distributeElements([
{
elements: [ customShape ],
range: {
min: 100,
max: 200
}
}
], 'x', 'width');
================================================
FILE: lib/features/modeling/behavior/AdaptiveLabelPositioningBehavior.js
================================================
import inherits from 'inherits-browser';
import {
getOrientation,
getMid,
asTRBL
} from 'diagram-js/lib/layout/LayoutUtil';
import {
substract
} from 'diagram-js/lib/util/Math';
import {
hasExternalLabel
} from '../../../util/LabelUtil';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import { isConnection } from 'diagram-js/lib/util/ModelUtil';
/**
* @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
* @typedef {import('../Modeling').default} Modeling
*
* @typedef {import('../../../model/Types').Element} Element
* @typedef {import('../../../model/Types').Shape} Shape
*
* @typedef {import('diagram-js/lib/util/Types').DirectionTRBL} DirectionTRBL
*/
var ALIGNMENTS = [
'top',
'bottom',
'left',
'right'
];
var ELEMENT_LABEL_DISTANCE = 10;
/**
* A behavior that ensures that labels are positioned in a way that they do not
* overlap with other elements or connections.
*
* @param {EventBus} eventBus
* @param {Modeling} modeling
*/
export default function AdaptiveLabelPositioningBehavior(eventBus, modeling) {
CommandInterceptor.call(this, eventBus);
this.postExecuted([
'connection.create',
'connection.layout',
'connection.updateWaypoints'
], function(event) {
var context = event.context,
connection = context.connection,
source = connection.source,
target = connection.target,
hints = context.hints || {};
if (hints.createElementsBehavior !== false) {
checkLabelAdjustment(source);
checkLabelAdjustment(target);
}
});
this.postExecuted([
'label.create'
], function(event) {
var context = event.context,
shape = context.shape,
hints = context.hints || {};
if (hints.createElementsBehavior !== false) {
checkLabelAdjustment(shape.labelTarget);
}
});
this.postExecuted([
'elements.create'
], function(event) {
var context = event.context,
elements = context.elements,
hints = context.hints || {};
if (hints.createElementsBehavior !== false) {
elements.forEach(function(element) {
checkLabelAdjustment(element);
});
}
});
function checkLabelAdjustment(element) {
// skip non-existing labels
if (!hasExternalLabel(element)) {
return;
}
if (isConnection(element)) {
return;
}
var optimalPosition = getOptimalPosition(element);
// no optimal position found
if (!optimalPosition) {
return;
}
adjustLabelPosition(element, optimalPosition);
}
function adjustLabelPosition(element, orientation) {
var elementMid = getMid(element),
label = element.label,
labelMid = getMid(label);
// ignore labels that are being created
if (!label.parent) {
return;
}
var elementTrbl = asTRBL(element);
var newLabelMid;
switch (orientation) {
case 'top':
newLabelMid = {
x: elementMid.x,
y: elementTrbl.top - ELEMENT_LABEL_DISTANCE - label.height / 2
};
break;
case 'left':
newLabelMid = {
x: elementTrbl.left - ELEMENT_LABEL_DISTANCE - label.width / 2,
y: elementMid.y
};
break;
case 'bottom':
newLabelMid = {
x: elementMid.x,
y: elementTrbl.bottom + ELEMENT_LABEL_DISTANCE + label.height / 2
};
break;
case 'right':
newLabelMid = {
x: elementTrbl.right + ELEMENT_LABEL_DISTANCE + label.width / 2,
y: elementMid.y
};
break;
}
var delta = substract(newLabelMid, labelMid);
modeling.moveShape(label, delta);
}
}
inherits(AdaptiveLabelPositioningBehavior, CommandInterceptor);
AdaptiveLabelPositioningBehavior.$inject = [
'eventBus',
'modeling'
];
// helpers //////////////////////
/**
* Return alignments which are taken by a boundary's host element
*
* @param {Shape} element
*
* @return {DirectionTRBL[]}
*/
function getTakenHostAlignments(element) {
var hostElement = element.host,
elementMid = getMid(element),
hostOrientation = getOrientation(elementMid, hostElement);
var freeAlignments;
// check whether there is a multi-orientation, e.g. 'top-left'
if (hostOrientation.indexOf('-') >= 0) {
freeAlignments = hostOrientation.split('-');
} else {
freeAlignments = [ hostOrientation ];
}
var takenAlignments = ALIGNMENTS.filter(function(alignment) {
return freeAlignments.indexOf(alignment) === -1;
});
return takenAlignments;
}
/**
* Return alignments which are taken by related connections
*
* @param {Element} element
*
* @return {DirectionTRBL[]}
*/
function getTakenConnectionAlignments(element) {
var elementMid = getMid(element);
var takenAlignments = [].concat(
element.incoming.map(function(c) {
return c.waypoints[c.waypoints.length - 2 ];
}),
element.outgoing.map(function(c) {
return c.waypoints[1];
})
).map(function(point) {
return getApproximateOrientation(elementMid, point);
});
return takenAlignments;
}
/**
* Return the optimal label position around an element
* or `undefined`, if none was found.
*
* @param {Element} element
*
* @return {DirectionTRBL|undefined}
*/
function getOptimalPosition(element) {
var labelMid = getMid(element.label);
var elementMid = getMid(element);
var labelOrientation = getApproximateOrientation(elementMid, labelMid);
if (!isAligned(labelOrientation)) {
return;
}
var takenAlignments = getTakenConnectionAlignments(element);
if (element.host) {
var takenHostAlignments = getTakenHostAlignments(element);
takenAlignments = takenAlignments.concat(takenHostAlignments);
}
var freeAlignments = ALIGNMENTS.filter(function(alignment) {
return takenAlignments.indexOf(alignment) === -1;
});
// NOTHING TO DO; label already aligned a.O.K.
if (freeAlignments.indexOf(labelOrientation) !== -1) {
return;
}
return freeAlignments[0];
}
function getApproximateOrientation(p0, p1) {
return getOrientation(p1, p0, 5);
}
function isAligned(orientation) {
return ALIGNMENTS.indexOf(orientation) !== -1;
}
================================================
FILE: lib/features/modeling/behavior/AppendBehavior.js
================================================
import inherits from 'inherits-browser';
import { is } from '../../../util/ModelUtil';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
/**
* @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
*/
export default function AppendBehavior(eventBus) {
CommandInterceptor.call(this, eventBus);
// assign correct shape position unless already set
this.preExecute('shape.append', function(context) {
var source = context.source,
shape = context.shape;
if (!context.position) {
if (is(shape, 'bpmn:TextAnnotation')) {
context.position = {
x: source.x + source.width / 2 + 75,
y: source.y - 50 - shape.height / 2
};
} else {
context.position = {
x: source.x + source.width + 80 + shape.width / 2,
y: source.y + source.height / 2
};
}
}
}, true);
}
inherits(AppendBehavior, CommandInterceptor);
AppendBehavior.$inject = [
'eventBus'
];
================================================
FILE: lib/features/modeling/behavior/AssociationBehavior.js
================================================
import inherits from 'inherits-browser';
import { is } from '../../../util/ModelUtil';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import {
filter,
forEach
} from 'min-dash';
/**
* @typedef {import('didi').Injector} Injector
* @typedef {import('../Modeling').default} Modeling
*/
/**
* @param {Injector} injector
* @param {Modeling} modeling
*/
export default function AssociationBehavior(injector, modeling) {
injector.invoke(CommandInterceptor, this);
this.postExecute('shape.move', function(context) {
var newParent = context.newParent,
shape = context.shape;
var associations = filter(shape.incoming.concat(shape.outgoing), function(connection) {
return is(connection, 'bpmn:Association');
});
forEach(associations, function(association) {
modeling.moveConnection(association, { x: 0, y: 0 }, newParent);
});
}, true);
}
inherits(AssociationBehavior, CommandInterceptor);
AssociationBehavior.$inject = [
'injector',
'modeling'
];
================================================
FILE: lib/features/modeling/behavior/AttachEventBehavior.js
================================================
import inherits from 'inherits-browser';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import { getBusinessObject } from '../../../util/ModelUtil';
import { isAny } from '../util/ModelingUtil';
import { isLabel } from '../../../util/LabelUtil';
/**
* @typedef {import('../../replace/BpmnReplace').default} BpmnReplace
* @typedef {import('didi').Injector} Injector
*/
var LOW_PRIORITY = 500;
/**
* Replace intermediate event with boundary event when creating or moving results in attached event.
*
* @param {BpmnReplace} bpmnReplace
* @param {Injector} injector
*/
export default function AttachEventBehavior(bpmnReplace, injector) {
injector.invoke(CommandInterceptor, this);
this._bpmnReplace = bpmnReplace;
var self = this;
this.postExecuted('elements.create', LOW_PRIORITY, function(context) {
var elements = context.elements;
elements = elements.filter(function(shape) {
var host = shape.host;
return shouldReplace(shape, host);
});
if (elements.length !== 1) {
return;
}
elements.map(function(element) {
return elements.indexOf(element);
}).forEach(function(index) {
var host = elements[ index ];
context.elements[ index ] = self._replaceShape(elements[ index ], host);
});
}, true);
this.preExecute('elements.move', LOW_PRIORITY, function(context) {
var shapes = context.shapes,
host = context.newHost;
if (shapes.length !== 1) {
return;
}
var shape = shapes[0];
if (shouldReplace(shape, host)) {
context.shapes = [ self._replaceShape(shape, host) ];
}
}, true);
}
AttachEventBehavior.$inject = [
'bpmnReplace',
'injector'
];
inherits(AttachEventBehavior, CommandInterceptor);
AttachEventBehavior.prototype._replaceShape = function(shape, host) {
var eventDefinition = getEventDefinition(shape);
var boundaryEvent = {
type: 'bpmn:BoundaryEvent',
host: host
};
if (eventDefinition) {
boundaryEvent.eventDefinitionType = eventDefinition.$type;
}
return this._bpmnReplace.replaceElement(shape, boundaryEvent, { layoutConnection: false });
};
// helpers //////////
function getEventDefinition(element) {
var businessObject = getBusinessObject(element),
eventDefinitions = businessObject.eventDefinitions;
return eventDefinitions && eventDefinitions[0];
}
function shouldReplace(shape, host) {
return !isLabel(shape) &&
isAny(shape, [ 'bpmn:IntermediateThrowEvent', 'bpmn:IntermediateCatchEvent' ]) && !!host;
}
================================================
FILE: lib/features/modeling/behavior/BoundaryEventBehavior.js
================================================
import inherits from 'inherits-browser';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import { is } from '../../../util/ModelUtil';
import {
filter,
forEach
} from 'min-dash';
/**
* @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
* @typedef {import('../Modeling').default} Modeling
*/
/**
* BPMN specific boundary event behavior.
*
* @param {EventBus} eventBus
* @param {Modeling} modeling
*/
export default function BoundaryEventBehavior(eventBus, modeling) {
CommandInterceptor.call(this, eventBus);
function getBoundaryEvents(element) {
return filter(element.attachers, function(attacher) {
return is(attacher, 'bpmn:BoundaryEvent');
});
}
// remove after connecting to event-based gateway
this.postExecute('connection.create', function(event) {
var source = event.context.source,
target = event.context.target,
boundaryEvents = getBoundaryEvents(target);
if (
is(source, 'bpmn:EventBasedGateway') &&
is(target, 'bpmn:ReceiveTask') &&
boundaryEvents.length > 0
) {
modeling.removeElements(boundaryEvents);
}
});
// remove after replacing connected gateway with event-based gateway
this.postExecute('connection.reconnect', function(event) {
var oldSource = event.context.oldSource,
newSource = event.context.newSource;
if (is(oldSource, 'bpmn:Gateway') &&
is(newSource, 'bpmn:EventBasedGateway')) {
forEach(newSource.outgoing, function(connection) {
var target = connection.target,
attachedboundaryEvents = getBoundaryEvents(target);
if (is(target, 'bpmn:ReceiveTask') &&
attachedboundaryEvents.length > 0) {
modeling.removeElements(attachedboundaryEvents);
}
});
}
});
}
BoundaryEventBehavior.$inject = [
'eventBus',
'modeling'
];
inherits(BoundaryEventBehavior, CommandInterceptor);
================================================
FILE: lib/features/modeling/behavior/CompensateBoundaryEventBehavior.js
================================================
import inherits from 'inherits-browser';
import { getBusinessObject, is } from '../../../util/ModelUtil';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import { hasEventDefinition, isEventSubProcess } from '../../../util/DiUtil';
/**
* @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
* @typedef {import('../lib/features/modeling/Modeling').default} Modeling
*/
/**
* Behavior ensuring that only a single compensation activity is connected to a
* compensation boundary event when connecting, reconnecting or replacing shapes.
*
* @param {import('diagram-js/lib/core/EventBus').default} eventBus
* @param {import('../Modeling').default} modeling
* @param {import('../../rules/BpmnRules').default} bpmnRules
*/
export default function CompensateBoundaryEventBehavior(eventBus, modeling, bpmnRules) {
CommandInterceptor.call(this, eventBus);
this.preExecute('shape.replace', handleReplacement, true);
this.postExecuted('shape.replace', handleReplacementPostExecuted, true);
this.preExecute('connection.create', handleNewConnection, true);
this.postExecuted('connection.delete', handleConnectionRemoval, true);
this.postExecuted('connection.reconnect', handleReconnection, true);
this.postExecuted('element.updateProperties', handlePropertiesUpdate, true);
/**
* Given a connection from boundary event is removed, remove the `isForCompensation` property.
*/
function handleConnectionRemoval(context) {
const source = context.source,
target = context.target;
if (isCompensationBoundaryEvent(source) && isForCompensation(target)) {
removeIsForCompensationProperty(target);
}
}
/**
* Add `isForCompensation` property and make sure only a single compensation activity is connected.
*/
function handleNewConnection(context) {
const connection = context.connection,
source = context.source,
target = context.target;
if (isCompensationBoundaryEvent(source) && isForCompensationAllowed(target)) {
addIsForCompensationProperty(target);
removeExistingAssociations(source, [ connection ]);
}
}
function handleReconnection(context) {
const newTarget = context.newTarget,
oldSource = context.oldSource,
oldTarget = context.oldTarget;
// target changes
if (oldTarget !== newTarget) {
const source = oldSource;
// oldTarget perspective
if (isForCompensation(oldTarget)) {
removeIsForCompensationProperty(oldTarget);
}
// newTarget perspective
if (isCompensationBoundaryEvent(source) && isForCompensationAllowed(newTarget)) {
addIsForCompensationProperty(newTarget);
}
}
}
function handlePropertiesUpdate(context) {
const { element } = context;
if (isForCompensation(element)) {
removeDisallowedConnections(element);
removeAttachments(element);
} else if (isForCompensationAllowed(element)) {
removeIncomingCompensationAssociations(element);
}
}
/**
* When replacing a boundary event, make sure the compensation activity is connected,
* and remove the potential candidates for connection replacement to have a single compensation activity.
*/
function handleReplacement(context) {
const {
newData,
oldShape
} = context;
// from compensate boundary event
if (isCompensationBoundaryEvent(context.oldShape) &&
newData.eventDefinitionType !== 'bpmn:CompensateEventDefinition' ||
newData.type !== 'bpmn:BoundaryEvent'
) {
const targetConnection = oldShape.outgoing.find(
({ target }) => isForCompensation(target)
);
if (targetConnection && targetConnection.target) {
context._connectionTarget = targetConnection.target;
}
}
// to compensate boundary event
else if (
!isCompensationBoundaryEvent(context.oldShape) &&
newData.eventDefinitionType === 'bpmn:CompensateEventDefinition' &&
newData.type === 'bpmn:BoundaryEvent'
) {
const targetConnection = oldShape.outgoing.find(
({ target }) => isForCompensationAllowed(target)
);
if (targetConnection && targetConnection.target) {
context._connectionTarget = targetConnection.target;
}
removeOutgoingSequenceFlows(oldShape);
}
}
function handleReplacementPostExecuted(context) {
const { _connectionTarget: target, newShape } = context;
if (target) {
modeling.connect(newShape, target);
}
}
function addIsForCompensationProperty(target) {
modeling.updateProperties(target, { isForCompensation: true });
}
function removeIsForCompensationProperty(target) {
modeling.updateProperties(target, { isForCompensation: undefined });
}
function removeDisallowedConnections(element) {
for (const connection of element.incoming) {
if (!bpmnRules.canConnect(connection.source, element)) {
modeling.removeConnection(connection);
}
}
for (const connection of element.outgoing) {
if (!bpmnRules.canConnect(element, connection.target)) {
modeling.removeConnection(connection);
}
}
}
function removeExistingAssociations(boundaryEvent, ignoredAssociations) {
const associations = boundaryEvent.outgoing.filter(connection => is(connection, 'bpmn:Association'));
const associationsToRemove = associations.filter(association => {
return isForCompensation(association.target) && !ignoredAssociations.includes(association);
});
// remove existing associations
associationsToRemove.forEach(association => modeling.removeConnection(association));
}
function removeAttachments(element) {
const attachments = element.attachers.slice();
if (!attachments.length) {
return;
}
modeling.removeElements(attachments);
}
function removeIncomingCompensationAssociations(element) {
const compensationAssociations = element.incoming.filter(
connection => isCompensationBoundaryEvent(connection.source)
);
modeling.removeElements(compensationAssociations);
}
function removeOutgoingSequenceFlows(element) {
const sequenceFlows = element.outgoing.filter(
connection => is(connection, 'bpmn:SequenceFlow')
);
modeling.removeElements(sequenceFlows);
}
}
inherits(CompensateBoundaryEventBehavior, CommandInterceptor);
CompensateBoundaryEventBehavior.$inject = [
'eventBus',
'modeling',
'bpmnRules'
];
// helpers //////////
function isForCompensation(element) {
const bo = getBusinessObject(element);
return bo && bo.get('isForCompensation');
}
function isCompensationBoundaryEvent(element) {
return element && is(element, 'bpmn:BoundaryEvent') &&
hasEventDefinition(element, 'bpmn:CompensateEventDefinition');
}
function isForCompensationAllowed(element) {
return element && is(element, 'bpmn:Activity') && !isEventSubProcess(element);
}
================================================
FILE: lib/features/modeling/behavior/CreateBehavior.js
================================================
import inherits from 'inherits-browser';
import { is } from '../../../util/ModelUtil';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import { getParent } from '../util/ModelingUtil';
/**
* @typedef {import('didi').Injector} Injector
*/
/**
* @param {Injector} injector
*/
export default function CreateBehavior(injector) {
injector.invoke(CommandInterceptor, this);
this.preExecute('shape.create', 1500, function(event) {
var context = event.context,
parent = context.parent,
shape = context.shape;
if (is(parent, 'bpmn:Lane') && !is(shape, 'bpmn:Lane')) {
context.parent = getParent(parent, 'bpmn:Participant');
}
});
}
CreateBehavior.$inject = [ 'injector' ];
inherits(CreateBehavior, CommandInterceptor);
================================================
FILE: lib/features/modeling/behavior/CreateDataObjectBehavior.js
================================================
import inherits from 'inherits-browser';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import { is } from '../../../util/ModelUtil';
/**
* @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
* @typedef {import('../BpmnFactory').default} BpmnFactory
*/
/**
* BPMN specific create data object behavior.
*
* @param {EventBus} eventBus
* @param {BpmnFactory} bpmnFactory
*/
export default function CreateDataObjectBehavior(eventBus, bpmnFactory) {
CommandInterceptor.call(this, eventBus);
this.preExecute('shape.create', function(event) {
var context = event.context,
shape = context.shape;
if (is(shape, 'bpmn:DataObjectReference') && shape.type !== 'label') {
// create a DataObject every time a DataObjectReference is created
var dataObject = bpmnFactory.create('bpmn:DataObject');
// Copy the isCollection property if needed.
dataObject.isCollection = shape.businessObject.dataObjectRef?.isCollection || false;
// set the reference to the DataObject
shape.businessObject.dataObjectRef = dataObject;
}
});
}
CreateDataObjectBehavior.$inject = [
'eventBus',
'bpmnFactory'
];
inherits(CreateDataObjectBehavior, CommandInterceptor);
================================================
FILE: lib/features/modeling/behavior/CreateParticipantBehavior.js
================================================
import inherits from 'inherits-browser';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import { getBusinessObject, is } from '../../../util/ModelUtil';
import { isLabel } from '../../../util/LabelUtil';
import { getBBox } from 'diagram-js/lib/util/Elements';
import {
assign,
find
} from 'min-dash';
import { asTRBL } from 'diagram-js/lib/layout/LayoutUtil';
import { isConnection } from 'diagram-js/lib/util/ModelUtil';
/**
* @typedef {import('diagram-js/lib/core/Canvas').default} Canvas
* @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
* @typedef {import('../Modeling').default} Modeling
*/
var HORIZONTAL_PARTICIPANT_PADDING = 20,
VERTICAL_PARTICIPANT_PADDING = 20;
export var PARTICIPANT_BORDER_WIDTH = 30;
var HIGH_PRIORITY = 2000;
/**
* BPMN-specific behavior for creating participants.
*
* @param {Canvas} canvas
* @param {EventBus} eventBus
* @param {Modeling} modeling
*/
export default function CreateParticipantBehavior(canvas, eventBus, modeling) {
CommandInterceptor.call(this, eventBus);
// fit participant
eventBus.on([
'create.start',
'shape.move.start'
], HIGH_PRIORITY, function(event) {
var context = event.context,
shape = context.shape,
rootElement = canvas.getRootElement();
if (!is(shape, 'bpmn:Participant') ||
!is(rootElement, 'bpmn:Process') ||
!rootElement.children.length) {
return;
}
// ignore connections, groups and labels
var children = rootElement.children.filter(function(element) {
return !is(element, 'bpmn:Group') &&
!isLabel(element) &&
!isConnection(element);
});
// ensure for available children to calculate bounds
if (!children.length) {
return;
}
var childrenBBox = getBBox(children);
var participantBounds = getParticipantBounds(shape, childrenBBox);
// assign width and height
assign(shape, participantBounds);
// assign create constraints
context.createConstraints = getParticipantCreateConstraints(shape, childrenBBox);
});
// force hovering process when creating first participant
eventBus.on('create.start', HIGH_PRIORITY, function(event) {
var context = event.context,
shape = context.shape,
rootElement = canvas.getRootElement(),
rootElementGfx = canvas.getGraphics(rootElement);
function ensureHoveringProcess(event) {
event.element = rootElement;
event.gfx = rootElementGfx;
}
if (is(shape, 'bpmn:Participant') && is(rootElement, 'bpmn:Process')) {
eventBus.on('element.hover', HIGH_PRIORITY, ensureHoveringProcess);
eventBus.once('create.cleanup', function() {
eventBus.off('element.hover', ensureHoveringProcess);
});
}
});
// turn process into collaboration when creating first participant
function getOrCreateCollaboration() {
var rootElement = canvas.getRootElement();
if (is(rootElement, 'bpmn:Collaboration')) {
return rootElement;
}
return modeling.makeCollaboration();
}
// when creating mutliple elements through `elements.create` parent must be set to collaboration
// and passed to `shape.create` as hint
this.preExecute('elements.create', HIGH_PRIORITY, function(context) {
var elements = context.elements,
parent = context.parent,
participant = findParticipant(elements),
hints;
if (participant && is(parent, 'bpmn:Process')) {
context.parent = getOrCreateCollaboration();
hints = context.hints = context.hints || {};
hints.participant = participant;
hints.process = parent;
hints.processRef = getBusinessObject(participant).get('processRef');
}
}, true);
// when creating single shape through `shape.create` parent must be set to collaboration
// unless it was already set through `elements.create`
this.preExecute('shape.create', function(context) {
var parent = context.parent,
shape = context.shape;
if (is(shape, 'bpmn:Participant') && is(parent, 'bpmn:Process')) {
context.parent = getOrCreateCollaboration();
context.process = parent;
context.processRef = getBusinessObject(shape).get('processRef');
}
}, true);
// #execute necessary because #preExecute not called on CommandStack#redo
this.execute('shape.create', function(context) {
var hints = context.hints || {},
process = context.process || hints.process,
shape = context.shape,
participant = hints.participant;
// both shape.create and elements.create must be handled
if (process && (!participant || shape === participant)) {
// monkey-patch process ref
getBusinessObject(shape).set('processRef', getBusinessObject(process));
}
}, true);
this.revert('shape.create', function(context) {
var hints = context.hints || {},
process = context.process || hints.process,
processRef = context.processRef || hints.processRef,
shape = context.shape,
participant = hints.participant;
// both shape.create and elements.create must be handled
if (process && (!participant || shape === participant)) {
// monkey-patch process ref
getBusinessObject(shape).set('processRef', processRef);
}
}, true);
this.postExecute('shape.create', function(context) {
var hints = context.hints || {},
process = context.process || context.hints.process,
shape = context.shape,
participant = hints.participant;
if (process) {
var children = process.children.slice();
// both shape.create and elements.create must be handled
if (!participant) {
modeling.moveElements(children, { x: 0, y: 0 }, shape);
} else if (shape === participant) {
modeling.moveElements(children, { x: 0, y: 0 }, participant);
}
}
}, true);
}
CreateParticipantBehavior.$inject = [
'canvas',
'eventBus',
'modeling'
];
inherits(CreateParticipantBehavior, CommandInterceptor);
// helpers //////////
function getParticipantBounds(shape, childrenBBox) {
childrenBBox = {
width: childrenBBox.width + HORIZONTAL_PARTICIPANT_PADDING * 2 + PARTICIPANT_BORDER_WIDTH,
height: childrenBBox.height + VERTICAL_PARTICIPANT_PADDING * 2
};
var width = Math.max(shape.width, childrenBBox.width),
height = Math.max(shape.height, childrenBBox.height);
return {
x: -width / 2,
y: -height / 2,
width: width,
height: height
};
}
function getParticipantCreateConstraints(shape, childrenBBox) {
childrenBBox = asTRBL(childrenBBox);
return {
bottom: childrenBBox.top + shape.height / 2 - VERTICAL_PARTICIPANT_PADDING,
left: childrenBBox.right - shape.width / 2 + HORIZONTAL_PARTICIPANT_PADDING,
top: childrenBBox.bottom - shape.height / 2 + VERTICAL_PARTICIPANT_PADDING,
right: childrenBBox.left + shape.width / 2 - HORIZONTAL_PARTICIPANT_PADDING - PARTICIPANT_BORDER_WIDTH
};
}
function findParticipant(elements) {
return find(elements, function(element) {
return is(element, 'bpmn:Participant');
});
}
================================================
FILE: lib/features/modeling/behavior/DataInputAssociationBehavior.js
================================================
import inherits from 'inherits-browser';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import {
add as collectionAdd,
remove as collectionRemove
} from 'diagram-js/lib/util/Collections';
import {
find
} from 'min-dash';
import {
is
} from '../../../util/ModelUtil';
/**
* @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
* @typedef {import('../BpmnFactory').default} BpmnFactory
*/
var TARGET_REF_PLACEHOLDER_NAME = '__targetRef_placeholder';
/**
* This behavior makes sure we always set a fake
* DataInputAssociation#targetRef as demanded by the BPMN 2.0
* XSD schema.
*
* The reference is set to a bpmn:Property{ name: '__targetRef_placeholder' }
* which is created on the fly and cleaned up afterwards if not needed
* anymore.
*
* @param {EventBus} eventBus
* @param {BpmnFactory} bpmnFactory
*/
export default function DataInputAssociationBehavior(eventBus, bpmnFactory) {
CommandInterceptor.call(this, eventBus);
this.executed([
'connection.create',
'connection.delete',
'connection.move',
'connection.reconnect'
], ifDataInputAssociation(fixTargetRef));
this.reverted([
'connection.create',
'connection.delete',
'connection.move',
'connection.reconnect'
], ifDataInputAssociation(fixTargetRef));
function usesTargetRef(element, targetRef, removedConnection) {
var inputAssociations = element.get('dataInputAssociations');
return find(inputAssociations, function(association) {
return association !== removedConnection &&
association.targetRef === targetRef;
});
}
function getTargetRef(element, create) {
var properties = element.get('properties');
var targetRefProp = find(properties, function(p) {
return p.name === TARGET_REF_PLACEHOLDER_NAME;
});
if (!targetRefProp && create) {
targetRefProp = bpmnFactory.create('bpmn:Property', {
name: TARGET_REF_PLACEHOLDER_NAME
});
collectionAdd(properties, targetRefProp);
}
return targetRefProp;
}
function cleanupTargetRef(element, connection) {
var targetRefProp = getTargetRef(element);
if (!targetRefProp) {
return;
}
if (!usesTargetRef(element, targetRefProp, connection)) {
collectionRemove(element.get('properties'), targetRefProp);
}
}
/**
* Make sure targetRef is set to a valid property or
* `null` if the connection is detached.
*
* @param {Event} event
*/
function fixTargetRef(event) {
var context = event.context,
connection = context.connection,
connectionBo = connection.businessObject,
target = connection.target,
targetBo = target && target.businessObject,
newTarget = context.newTarget,
newTargetBo = newTarget && newTarget.businessObject,
oldTarget = context.oldTarget || context.target,
oldTargetBo = oldTarget && oldTarget.businessObject;
var dataAssociation = connection.businessObject,
targetRefProp;
if (oldTargetBo && oldTargetBo !== targetBo) {
cleanupTargetRef(oldTargetBo, connectionBo);
}
if (newTargetBo && newTargetBo !== targetBo) {
cleanupTargetRef(newTargetBo, connectionBo);
}
if (targetBo) {
targetRefProp = getTargetRef(targetBo, true);
dataAssociation.targetRef = targetRefProp;
} else {
dataAssociation.targetRef = null;
}
}
}
DataInputAssociationBehavior.$inject = [
'eventBus',
'bpmnFactory'
];
inherits(DataInputAssociationBehavior, CommandInterceptor);
/**
* Only call the given function when the event
* changes a bpmn:DataInputAssociation.
*
* @param {Function} fn
* @return {Function}
*/
function ifDataInputAssociation(fn) {
return function(event) {
var context = event.context,
connection = context.connection;
if (is(connection, 'bpmn:DataInputAssociation')) {
return fn(event);
}
};
}
================================================
FILE: lib/features/modeling/behavior/DataStoreBehavior.js
================================================
import inherits from 'inherits-browser';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import {
getBusinessObject,
getDi,
is
} from '../../../util/ModelUtil';
import { isAny } from '../util/ModelingUtil';
import UpdateSemanticParentHandler from '../cmd/UpdateSemanticParentHandler';
/**
* @typedef {import('diagram-js/lib/core/Canvas').default} Canvas
* @typedef {import('diagram-js/lib/command/CommandStack').default} CommandStack
* @typedef {import('diagram-js/lib/core/ElementRegistry').default} ElementRegistry
* @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
*/
/**
* BPMN specific data store behavior.
*
* @param {Canvas} canvas
* @param {CommandStack} commandStack
* @param {ElementRegistry} elementRegistry
* @param {EventBus} eventBus
*/
export default function DataStoreBehavior(
canvas, commandStack, elementRegistry,
eventBus) {
CommandInterceptor.call(this, eventBus);
commandStack.registerHandler('dataStore.updateContainment', UpdateSemanticParentHandler);
function getFirstParticipantWithProcessRef() {
return elementRegistry.filter(function(element) {
return is(element, 'bpmn:Participant') && getBusinessObject(element).processRef;
})[0];
}
function getDataStores(element) {
return element.children.filter(function(child) {
return is(child, 'bpmn:DataStoreReference') && !child.labelTarget;
});
}
function updateDataStoreParent(dataStore, newDataStoreParent) {
var dataStoreBo = dataStore.businessObject || dataStore;
newDataStoreParent = newDataStoreParent || getFirstParticipantWithProcessRef();
if (newDataStoreParent) {
var newDataStoreParentBo = newDataStoreParent.businessObject || newDataStoreParent;
commandStack.execute('dataStore.updateContainment', {
dataStoreBo: dataStoreBo,
dataStoreDi: getDi(dataStore),
newSemanticParent: newDataStoreParentBo.processRef || newDataStoreParentBo,
newDiParent: getDi(newDataStoreParent)
});
}
}
// disable auto-resize for data stores
this.preExecute('shape.create', function(event) {
var context = event.context,
shape = context.shape;
if (is(shape, 'bpmn:DataStoreReference') &&
shape.type !== 'label') {
if (!context.hints) {
context.hints = {};
}
// prevent auto resizing
context.hints.autoResize = false;
}
});
// disable auto-resize for data stores
this.preExecute('elements.move', function(event) {
var context = event.context,
shapes = context.shapes;
var dataStoreReferences = shapes.filter(function(shape) {
return is(shape, 'bpmn:DataStoreReference');
});
if (dataStoreReferences.length) {
if (!context.hints) {
context.hints = {};
}
// prevent auto resizing for data store references
context.hints.autoResize = shapes.filter(function(shape) {
return !is(shape, 'bpmn:DataStoreReference');
});
}
});
// update parent on data store created
this.postExecute('shape.create', function(event) {
var context = event.context,
shape = context.shape,
parent = shape.parent;
if (is(shape, 'bpmn:DataStoreReference') &&
shape.type !== 'label' &&
is(parent, 'bpmn:Collaboration')) {
updateDataStoreParent(shape);
}
});
// update parent on data store moved
this.postExecute('shape.move', function(event) {
var context = event.context,
shape = context.shape,
oldParent = context.oldParent,
parent = shape.parent;
if (is(oldParent, 'bpmn:Collaboration')) {
// do nothing if not necessary
return;
}
if (is(shape, 'bpmn:DataStoreReference') &&
shape.type !== 'label' &&
is(parent, 'bpmn:Collaboration')) {
var participant = is(oldParent, 'bpmn:Participant') ?
oldParent :
getAncestor(oldParent, 'bpmn:Participant');
updateDataStoreParent(shape, participant);
}
});
// update data store parents on participant or subprocess deleted
this.postExecute('shape.delete', function(event) {
var context = event.context,
shape = context.shape,
rootElement = canvas.getRootElement();
if (isAny(shape, [ 'bpmn:Participant', 'bpmn:SubProcess' ])
&& is(rootElement, 'bpmn:Collaboration')) {
getDataStores(rootElement)
.filter(function(dataStore) {
return isDescendant(dataStore, shape);
})
.forEach(function(dataStore) {
updateDataStoreParent(dataStore);
});
}
});
// update data store parents on collaboration -> process
this.postExecute('canvas.updateRoot', function(event) {
var context = event.context,
oldRoot = context.oldRoot,
newRoot = context.newRoot;
var dataStores = getDataStores(oldRoot);
dataStores.forEach(function(dataStore) {
if (is(newRoot, 'bpmn:Process')) {
updateDataStoreParent(dataStore, newRoot);
}
});
});
}
DataStoreBehavior.$inject = [
'canvas',
'commandStack',
'elementRegistry',
'eventBus',
];
inherits(DataStoreBehavior, CommandInterceptor);
// helpers //////////
function isDescendant(descendant, ancestor) {
var descendantBo = descendant.businessObject || descendant,
ancestorBo = ancestor.businessObject || ancestor;
while (descendantBo.$parent) {
if (descendantBo.$parent === ancestorBo.processRef || ancestorBo) {
return true;
}
descendantBo = descendantBo.$parent;
}
return false;
}
function getAncestor(element, type) {
while (element.parent) {
if (is(element.parent, type)) {
return element.parent;
}
element = element.parent;
}
}
================================================
FILE: lib/features/modeling/behavior/DeleteLaneBehavior.js
================================================
import inherits from 'inherits-browser';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import { is } from '../../../util/ModelUtil';
import {
getChildLanes
} from '../util/LaneUtil';
import {
isHorizontal
} from '../../../util/DiUtil';
import {
eachElement
} from 'diagram-js/lib/util/Elements';
/**
* @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
* @typedef {import('../../space-tool/BpmnSpaceTool').default} SpaceTool
*/
var LOW_PRIORITY = 500;
/**
* BPMN specific delete lane behavior.
*
* @param {EventBus} eventBus
* @param {SpaceTool} spaceTool
*/
export default function DeleteLaneBehavior(eventBus, spaceTool) {
CommandInterceptor.call(this, eventBus);
function compensateLaneDelete(shape, oldParent) {
var isHorizontalLane = isHorizontal(shape);
var siblings = getChildLanes(oldParent);
var topAffected = [];
var bottomAffected = [];
var leftAffected = [];
var rightAffected = [];
eachElement(siblings, function(element) {
if (isHorizontalLane) {
if (element.y > shape.y) {
bottomAffected.push(element);
} else {
topAffected.push(element);
}
} else {
if (element.x > shape.x) {
rightAffected.push(element);
} else {
leftAffected.push(element);
}
}
return element.children;
});
if (!siblings.length) {
return;
}
var offset;
if (isHorizontalLane) {
if (bottomAffected.length && topAffected.length) {
offset = shape.height / 2;
} else {
offset = shape.height;
}
} else {
if (rightAffected.length && leftAffected.length) {
offset = shape.width / 2;
} else {
offset = shape.width;
}
}
var topAdjustments,
bottomAdjustments,
leftAdjustments,
rightAdjustments;
if (topAffected.length) {
topAdjustments = spaceTool.calculateAdjustments(
topAffected, 'y', offset, shape.y - 10);
spaceTool.makeSpace(
topAdjustments.movingShapes,
topAdjustments.resizingShapes,
{ x: 0, y: offset }, 's');
}
if (bottomAffected.length) {
bottomAdjustments = spaceTool.calculateAdjustments(
bottomAffected, 'y', -offset, shape.y + shape.height + 10);
spaceTool.makeSpace(
bottomAdjustments.movingShapes,
bottomAdjustments.resizingShapes,
{ x: 0, y: -offset }, 'n');
}
if (leftAffected.length) {
leftAdjustments = spaceTool.calculateAdjustments(
leftAffected, 'x', offset, shape.x - 10);
spaceTool.makeSpace(
leftAdjustments.movingShapes,
leftAdjustments.resizingShapes,
{ x: offset, y: 0 }, 'e');
}
if (rightAffected.length) {
rightAdjustments = spaceTool.calculateAdjustments(
rightAffected, 'x', -offset, shape.x + shape.width + 10);
spaceTool.makeSpace(
rightAdjustments.movingShapes,
rightAdjustments.resizingShapes,
{ x: -offset, y: 0 }, 'w');
}
}
/**
* Adjust sizes of other lanes after lane deletion
*/
this.postExecuted('shape.delete', LOW_PRIORITY, function(event) {
var context = event.context,
hints = context.hints,
shape = context.shape,
oldParent = context.oldParent;
// only compensate lane deletes
if (!is(shape, 'bpmn:Lane')) {
return;
}
// compensate root deletes only
if (hints && hints.nested) {
return;
}
compensateLaneDelete(shape, oldParent);
});
}
DeleteLaneBehavior.$inject = [
'eventBus',
'spaceTool'
];
inherits(DeleteLaneBehavior, CommandInterceptor);
================================================
FILE: lib/features/modeling/behavior/DetachEventBehavior.js
================================================
import inherits from 'inherits-browser';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import {
getBusinessObject,
is
} from '../../../util/ModelUtil';
import { isLabel } from '../../../util/LabelUtil';
/**
* @typedef {import('../../replace/BpmnReplace').default} BpmnReplace
* @typedef {import('didi').Injector} Injector
*/
var LOW_PRIORITY = 500;
/**
* Replace boundary event with intermediate event when creating or moving results in detached event.
*
* @param {BpmnReplace} bpmnReplace
* @param {Injector} injector
*/
export default function DetachEventBehavior(bpmnReplace, injector) {
injector.invoke(CommandInterceptor, this);
this._bpmnReplace = bpmnReplace;
var self = this;
this.postExecuted('elements.create', LOW_PRIORITY, function(context) {
var elements = context.elements;
elements.filter(function(shape) {
var host = shape.host;
return shouldReplace(shape, host);
}).map(function(shape) {
return elements.indexOf(shape);
}).forEach(function(index) {
context.elements[ index ] = self._replaceShape(elements[ index ]);
});
}, true);
this.preExecute('elements.move', LOW_PRIORITY, function(context) {
var shapes = context.shapes,
newHost = context.newHost;
shapes.forEach(function(shape, index) {
var host = shape.host;
if (shouldReplace(shape, includes(shapes, host) ? host : newHost)) {
shapes[ index ] = self._replaceShape(shape);
}
});
}, true);
}
DetachEventBehavior.$inject = [
'bpmnReplace',
'injector'
];
inherits(DetachEventBehavior, CommandInterceptor);
DetachEventBehavior.prototype._replaceShape = function(shape) {
var eventDefinition = getEventDefinition(shape),
intermediateEvent;
if (eventDefinition) {
intermediateEvent = {
type: 'bpmn:IntermediateCatchEvent',
eventDefinitionType: eventDefinition.$type
};
} else {
intermediateEvent = {
type: 'bpmn:IntermediateThrowEvent'
};
}
return this._bpmnReplace.replaceElement(shape, intermediateEvent, { layoutConnection: false });
};
// helpers //////////
function getEventDefinition(element) {
var businessObject = getBusinessObject(element),
eventDefinitions = businessObject.eventDefinitions;
return eventDefinitions && eventDefinitions[0];
}
function shouldReplace(shape, host) {
return !isLabel(shape) && is(shape, 'bpmn:BoundaryEvent') && !host;
}
function includes(array, item) {
return array.indexOf(item) !== -1;
}
================================================
FILE: lib/features/modeling/behavior/DropOnFlowBehavior.js
================================================
import inherits from 'inherits-browser';
import {
assign,
filter,
find,
isNumber
} from 'min-dash';
import { getMid } from 'diagram-js/lib/layout/LayoutUtil';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import {
getApproxIntersection
} from 'diagram-js/lib/util/LineIntersection';
/**
* @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
* @typedef {import('../../rules/BpmnRules').default} BpmnRules
* @typedef {import('../../modeling/Modeling').default} Modeling
*/
/**
* @param {EventBus} eventBus
* @param {BpmnRules} bpmnRules
* @param {Modeling} modeling
*/
export default function DropOnFlowBehavior(eventBus, bpmnRules, modeling) {
CommandInterceptor.call(this, eventBus);
/**
* Reconnect start / end of a connection after
* dropping an element on a flow.
*/
function insertShape(shape, targetFlow, positionOrBounds) {
var waypoints = targetFlow.waypoints,
waypointsBefore,
waypointsAfter,
dockingPoint,
source,
target,
incomingConnection,
outgoingConnection,
oldOutgoing = shape.outgoing.slice(),
oldIncoming = shape.incoming.slice();
var mid;
if (isNumber(positionOrBounds.width)) {
mid = getMid(positionOrBounds);
} else {
mid = positionOrBounds;
}
var intersection = getApproxIntersection(waypoints, mid);
if (intersection) {
waypointsBefore = waypoints.slice(0, intersection.index);
waypointsAfter = waypoints.slice(intersection.index + (intersection.bendpoint ? 1 : 0));
// due to inaccuracy intersection might have been found
if (!waypointsBefore.length || !waypointsAfter.length) {
return;
}
dockingPoint = intersection.bendpoint ? waypoints[intersection.index] : mid;
// if last waypointBefore is inside shape's bounds, ignore docking point
if (waypointsBefore.length === 1 || !isPointInsideBBox(shape, waypointsBefore[waypointsBefore.length - 1])) {
waypointsBefore.push(copy(dockingPoint));
}
// if first waypointAfter is inside shape's bounds, ignore docking point
if (waypointsAfter.length === 1 || !isPointInsideBBox(shape, waypointsAfter[0])) {
waypointsAfter.unshift(copy(dockingPoint));
}
}
source = targetFlow.source;
target = targetFlow.target;
if (bpmnRules.canConnect(source, shape, targetFlow)) {
// reconnect source -> inserted shape
modeling.reconnectEnd(targetFlow, shape, waypointsBefore || mid);
incomingConnection = targetFlow;
}
if (bpmnRules.canConnect(shape, target, targetFlow)) {
if (!incomingConnection) {
// reconnect inserted shape -> end
modeling.reconnectStart(targetFlow, shape, waypointsAfter || mid);
outgoingConnection = targetFlow;
} else {
outgoingConnection = modeling.connect(
shape, target, { type: targetFlow.type, waypoints: waypointsAfter }
);
}
}
var duplicateConnections = [].concat(
incomingConnection && filter(oldIncoming, function(connection) {
return connection.source === incomingConnection.source;
}) || [],
outgoingConnection && filter(oldOutgoing, function(connection) {
return connection.target === outgoingConnection.target;
}) || []
);
if (duplicateConnections.length) {
modeling.removeElements(duplicateConnections);
}
}
this.preExecute('elements.move', function(context) {
var newParent = context.newParent,
shapes = context.shapes,
delta = context.delta,
shape = shapes[0];
if (!shape || !newParent) {
return;
}
// if the new parent is a connection,
// change it to the new parent's parent
if (newParent && newParent.waypoints) {
context.newParent = newParent = newParent.parent;
}
var shapeMid = getMid(shape);
var newShapeMid = {
x: shapeMid.x + delta.x,
y: shapeMid.y + delta.y
};
// find a connection which intersects with the
// element's mid point
var connection = find(newParent.children, function(element) {
var canInsert = bpmnRules.canInsert(shapes, element);
return canInsert && getApproxIntersection(element.waypoints, newShapeMid);
});
if (connection) {
context.targetFlow = connection;
context.position = newShapeMid;
}
}, true);
this.postExecuted('elements.move', function(context) {
var shapes = context.shapes,
targetFlow = context.targetFlow,
position = context.position;
if (targetFlow) {
insertShape(shapes[0], targetFlow, position);
}
}, true);
this.preExecute('shape.create', function(context) {
var parent = context.parent,
shape = context.shape;
if (bpmnRules.canInsert(shape, parent)) {
context.targetFlow = parent;
context.parent = parent.parent;
}
}, true);
this.postExecuted('shape.create', function(context) {
var shape = context.shape,
targetFlow = context.targetFlow,
positionOrBounds = context.position;
if (targetFlow) {
insertShape(shape, targetFlow, positionOrBounds);
}
}, true);
}
inherits(DropOnFlowBehavior, CommandInterceptor);
DropOnFlowBehavior.$inject = [
'eventBus',
'bpmnRules',
'modeling'
];
// helpers /////////////////////
function isPointInsideBBox(bbox, point) {
var x = point.x,
y = point.y;
return x >= bbox.x &&
x <= bbox.x + bbox.width &&
y >= bbox.y &&
y <= bbox.y + bbox.height;
}
function copy(obj) {
return assign({}, obj);
}
================================================
FILE: lib/features/modeling/behavior/EventBasedGatewayBehavior.js
================================================
import inherits from 'inherits-browser';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import { is } from '../../../util/ModelUtil';
/**
* @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
* @typedef {import('../Modeling').default} Modeling
*/
/**
* @param {EventBus} eventBus
* @param {Modeling} modeling
*/
export default function EventBasedGatewayBehavior(eventBus, modeling) {
CommandInterceptor.call(this, eventBus);
/**
* Remove incoming sequence flows of event-based target when creating
* sequence flow.
*
* 1. If source is event-based gateway remove all incoming sequence flows
* 2. If source is not event-based gateway remove all incoming sequence flows
* whose source is event-based gateway
*/
this.preExecuted('connection.create', function(event) {
var context = event.context,
connection = context.connection,
source = context.source,
target = context.target,
hints = context.hints;
if (hints && hints.createElementsBehavior === false) {
return;
}
if (!isSequenceFlow(connection)) {
return;
}
var sequenceFlows = [];
if (is(source, 'bpmn:EventBasedGateway')) {
sequenceFlows = target.incoming
.filter(flow =>
flow !== connection &&
isSequenceFlow(flow)
);
} else {
sequenceFlows = target.incoming
.filter(flow =>
flow !== connection &&
isSequenceFlow(flow) &&
is(flow.source, 'bpmn:EventBasedGateway')
);
}
sequenceFlows.forEach(function(sequenceFlow) {
modeling.removeConnection(sequenceFlow);
});
});
/**
* Remove incoming sequence flows of event-based targets when replacing source
* with event-based gateway.
*/
this.preExecuted('shape.replace', function(event) {
var context = event.context,
newShape = context.newShape;
if (!is(newShape, 'bpmn:EventBasedGateway')) {
return;
}
var targets = newShape.outgoing.filter(isSequenceFlow)
.reduce(function(targets, sequenceFlow) {
if (!targets.includes(sequenceFlow.target)) {
return targets.concat(sequenceFlow.target);
}
return targets;
}, []);
targets.forEach(function(target) {
target.incoming.filter(isSequenceFlow).forEach(function(sequenceFlow) {
const sequenceFlowsFromNewShape = target.incoming.filter(isSequenceFlow).filter(function(sequenceFlow) {
return sequenceFlow.source === newShape;
});
if (sequenceFlow.source !== newShape || sequenceFlowsFromNewShape.length > 1) {
modeling.removeConnection(sequenceFlow);
}
});
});
});
}
EventBasedGatewayBehavior.$inject = [
'eventBus',
'modeling'
];
inherits(EventBasedGatewayBehavior, CommandInterceptor);
// helpers //////////
function isSequenceFlow(connection) {
return is(connection, 'bpmn:SequenceFlow');
}
================================================
FILE: lib/features/modeling/behavior/FixHoverBehavior.js
================================================
import { getLanesRoot } from '../util/LaneUtil';
import { is, isAny } from '../../../util/ModelUtil';
/**
* @typedef {import('diagram-js/lib/core/ElementRegistry').default} ElementRegistry
* @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
* @typedef {import('diagram-js/lib/core/Canvas').default} Canvas
*/
var HIGH_PRIORITY = 1500;
var HIGHEST_PRIORITY = 2000;
/**
* Correct hover targets in certain situations to improve diagram interaction.
*
* @param {ElementRegistry} elementRegistry
* @param {EventBus} eventBus
* @param {Canvas} canvas
*/
export default function FixHoverBehavior(elementRegistry, eventBus, canvas) {
eventBus.on([
'create.hover',
'create.move',
'create.out',
'create.end',
'shape.move.hover',
'shape.move.move',
'shape.move.out',
'shape.move.end'
], HIGH_PRIORITY, function(event) {
var context = event.context,
shape = context.shape || event.shape,
hover = event.hover;
// ensure elements are not dropped onto a bpmn:Lane but onto
// the underlying bpmn:Participant
if (is(hover, 'bpmn:Lane') && !isAny(shape, [ 'bpmn:Lane', 'bpmn:Participant' ])) {
event.hover = getLanesRoot(hover);
event.hoverGfx = elementRegistry.getGraphics(event.hover);
}
var rootElement = canvas.getRootElement();
// ensure bpmn:Group and label elements are dropped
// always onto the root
if (hover !== rootElement && (shape.labelTarget || isAny(shape, [ 'bpmn:Group', 'bpmn:TextAnnotation' ]))) {
event.hover = rootElement;
event.hoverGfx = elementRegistry.getGraphics(event.hover);
}
});
eventBus.on([
'connect.hover',
'connect.out',
'connect.end',
'connect.cleanup',
'global-connect.hover',
'global-connect.out',
'global-connect.end',
'global-connect.cleanup'
], HIGH_PRIORITY, function(event) {
var hover = event.hover;
// ensure connections start/end on bpmn:Participant,
// not the underlying bpmn:Lane
if (is(hover, 'bpmn:Lane')) {
event.hover = getLanesRoot(hover) || hover;
event.hoverGfx = elementRegistry.getGraphics(event.hover);
}
});
eventBus.on([
'bendpoint.move.hover'
], HIGH_PRIORITY, function(event) {
var context = event.context,
hover = event.hover,
type = context.type;
// ensure reconnect start/end on bpmn:Participant,
// not the underlying bpmn:Lane
if (is(hover, 'bpmn:Lane') && /reconnect/.test(type)) {
event.hover = getLanesRoot(hover) || hover;
event.hoverGfx = elementRegistry.getGraphics(event.hover);
}
});
eventBus.on([
'connect.start'
], HIGH_PRIORITY, function(event) {
var context = event.context,
start = context.start;
// ensure connect start on bpmn:Participant,
// not the underlying bpmn:Lane
if (is(start, 'bpmn:Lane')) {
context.start = getLanesRoot(start) || start;
}
});
// allow movement of participants from lanes
eventBus.on('shape.move.start', HIGHEST_PRIORITY, function(event) {
var shape = event.shape;
if (is(shape, 'bpmn:Lane')) {
event.shape = getLanesRoot(shape) || shape;
}
});
// ensure lanes aren't resized without their parent participant when using
// space tool
eventBus.on('spaceTool.move', HIGHEST_PRIORITY, function(event) {
var hover = event.hover;
if (hover && is(hover, 'bpmn:Lane')) {
event.hover = getLanesRoot(hover);
}
});
}
FixHoverBehavior.$inject = [
'elementRegistry',
'eventBus',
'canvas'
];
================================================
FILE: lib/features/modeling/behavior/GroupBehavior.js
================================================
import inherits from 'inherits-browser';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import {
getBusinessObject,
is
} from '../../../util/ModelUtil';
import {
createCategory,
createCategoryValue,
linkCategoryValue,
unlinkCategory,
unlinkCategoryValue
} from './util/CategoryUtil';
/**
* @typedef {import('../BpmnFactory').default} BpmnFactory
* @typedef {import('../../../Modeler').default} Modeler
* @typedef {import('diagram-js/lib/core/ElementRegistry').default} ElementRegistry
* @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
* @typedef {import('didi').Injector} Injector
* @typedef {import('../../copy-paste/ModdleCopy').default} ModdleCopy
*
* @typedef {import('../../../model/Types').Element} Element
* @typedef {import('../../../model/Types').Shape} Shape
*
* @typedef {import('diagram-js/lib/util/Types').DirectionTRBL} DirectionTRBL
*/
var LOWER_PRIORITY = 770;
/**
* BPMN specific group behavior.
*
* @param {BpmnFactory} bpmnFactory
* @param {Modeler} bpmnjs
* @param {ElementRegistry} elementRegistry
* @param {EventBus} eventBus
* @param {Injector} injector
* @param {ModdleCopy} moddleCopy
*/
export default function GroupBehavior(
bpmnFactory,
bpmnjs,
elementRegistry,
eventBus,
injector,
moddleCopy
) {
injector.invoke(CommandInterceptor, this);
/**
* Returns all group element in the current registry.
*
* @return {Shape[]}
*/
function getGroupElements() {
return elementRegistry.filter(function(e) {
return is(e, 'bpmn:Group');
});
}
/**
* Returns true if given category is referenced in one of the given elements.
*
* @param {Element[]} elements
* @param {ModdleElement} category
*
* @return {boolean}
*/
function isReferencedCategory(elements, category) {
return elements.some(function(element) {
var businessObject = getBusinessObject(element);
var _category = businessObject.categoryValueRef && businessObject.categoryValueRef.$parent;
return _category === category;
});
}
/**
* Returns true if given categoryValue is referenced in one of the given elements.
*
* @param {Element[]} elements
* @param {ModdleElement} categoryValue
*
* @return {boolean}
*/
function isReferencedCategoryValue(elements, categoryValue) {
return elements.some(function(element) {
var businessObject = getBusinessObject(element);
return businessObject.categoryValueRef === categoryValue;
});
}
/**
* Remove category value unless it is still referenced.
*
* @param {ModdleElement} categoryValue
* @param {ModdleElement} category
* @param {ModdleElement} businessObject
*/
function removeCategoryValue(categoryValue, category, businessObject) {
var groups = getGroupElements().filter(function(element) {
return element.businessObject !== businessObject;
});
if (category && !isReferencedCategory(groups, category)) {
unlinkCategory(category);
}
if (categoryValue && !isReferencedCategoryValue(groups, categoryValue)) {
unlinkCategoryValue(categoryValue);
}
}
/**
* Add category value.
*
* @param {ModdleElement} categoryValue
* @param {ModdleElement} category
*
* @return {ModdleElement}
*/
function addCategoryValue(categoryValue, category) {
return linkCategoryValue(categoryValue, category, bpmnjs.getDefinitions());
}
function setCategoryValue(element, context) {
var businessObject = getBusinessObject(element),
categoryValue = businessObject.categoryValueRef;
if (!categoryValue) {
categoryValue =
businessObject.categoryValueRef =
context.categoryValue = (
context.categoryValue || createCategoryValue(bpmnFactory)
);
}
var category = categoryValue.$parent;
if (!category) {
category =
categoryValue.$parent =
context.category = (
context.category || createCategory(bpmnFactory)
);
}
addCategoryValue(categoryValue, category, bpmnjs.getDefinitions());
}
function unsetCategoryValue(element, context) {
var category = context.category,
categoryValue = context.categoryValue,
businessObject = getBusinessObject(element);
if (categoryValue) {
businessObject.categoryValueRef = null;
removeCategoryValue(categoryValue, category, businessObject);
} else {
removeCategoryValue(null, businessObject.categoryValueRef.$parent, businessObject);
}
}
// ensure category + value exist before label editing
this.execute('label.create', function(event) {
var context = event.context,
labelTarget = context.labelTarget;
if (!is(labelTarget, 'bpmn:Group')) {
return;
}
setCategoryValue(labelTarget, context);
});
this.revert('label.create', function(event) {
var context = event.context,
labelTarget = context.labelTarget;
if (!is(labelTarget, 'bpmn:Group')) {
return;
}
unsetCategoryValue(labelTarget, context);
});
// remove referenced category + value when group was deleted
this.execute('shape.delete', function(event) {
var context = event.context,
shape = context.shape,
businessObject = getBusinessObject(shape);
if (!is(shape, 'bpmn:Group') || shape.labelTarget) {
return;
}
var categoryValue = context.categoryValue = businessObject.categoryValueRef,
category;
if (categoryValue) {
category = context.category = categoryValue.$parent;
removeCategoryValue(categoryValue, category, businessObject);
businessObject.categoryValueRef = null;
}
});
this.reverted('shape.delete', function(event) {
var context = event.context,
shape = context.shape;
if (!is(shape, 'bpmn:Group') || shape.labelTarget) {
return;
}
var category = context.category,
categoryValue = context.categoryValue,
businessObject = getBusinessObject(shape);
if (categoryValue) {
businessObject.categoryValueRef = categoryValue;
addCategoryValue(categoryValue, category);
}
});
// create new category + value when group was created
this.execute('shape.create', function(event) {
var context = event.context,
shape = context.shape;
if (!is(shape, 'bpmn:Group') || shape.labelTarget) {
return;
}
if (getBusinessObject(shape).categoryValueRef) {
setCategoryValue(shape, context);
}
});
this.reverted('shape.create', function(event) {
var context = event.context,
shape = context.shape;
if (!is(shape, 'bpmn:Group') || shape.labelTarget) {
return;
}
if (getBusinessObject(shape).categoryValueRef) {
unsetCategoryValue(shape, context);
}
});
// copy + paste categoryValueRef with group
function copy(bo, clone) {
var targetBo = bpmnFactory.create(bo.$type);
return moddleCopy.copyElement(bo, targetBo, null, clone);
}
eventBus.on('copyPaste.copyElement', LOWER_PRIORITY, function(context) {
var descriptor = context.descriptor,
element = context.element;
if (!is(element, 'bpmn:Group') || element.labelTarget) {
return;
}
var groupBo = getBusinessObject(element);
if (groupBo.categoryValueRef) {
var categoryValue = groupBo.categoryValueRef;
descriptor.categoryValue = copy(categoryValue, true);
if (categoryValue.$parent) {
descriptor.category = copy(categoryValue.$parent, true);
}
}
});
eventBus.on('copyPaste.pasteElement', LOWER_PRIORITY, function(context) {
var descriptor = context.descriptor,
businessObject = descriptor.businessObject,
categoryValue = descriptor.categoryValue,
category = descriptor.category;
if (categoryValue) {
categoryValue = businessObject.categoryValueRef = copy(categoryValue);
}
if (category) {
categoryValue.$parent = copy(category);
}
delete descriptor.category;
delete descriptor.categoryValue;
});
}
GroupBehavior.$inject = [
'bpmnFactory',
'bpmnjs',
'elementRegistry',
'eventBus',
'injector',
'moddleCopy'
];
inherits(GroupBehavior, CommandInterceptor);
================================================
FILE: lib/features/modeling/behavior/ImportDockingFix.js
================================================
import {
getMid
} from 'diagram-js/lib/layout/LayoutUtil';
import lineIntersect from './util/LineIntersect';
/**
* @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
*/
/**
* Fix broken dockings after DI imports.
*
* @param {EventBus} eventBus
*/
export default function ImportDockingFix(eventBus) {
function adjustDocking(startPoint, nextPoint, elementMid) {
var elementTop = {
x: elementMid.x,
y: elementMid.y - 50
};
var elementLeft = {
x: elementMid.x - 50,
y: elementMid.y
};
var verticalIntersect = lineIntersect(startPoint, nextPoint, elementMid, elementTop),
horizontalIntersect = lineIntersect(startPoint, nextPoint, elementMid, elementLeft);
// original is horizontal or vertical center cross intersection
var centerIntersect;
if (verticalIntersect && horizontalIntersect) {
if (getDistance(verticalIntersect, elementMid) > getDistance(horizontalIntersect, elementMid)) {
centerIntersect = horizontalIntersect;
} else {
centerIntersect = verticalIntersect;
}
} else {
centerIntersect = verticalIntersect || horizontalIntersect;
}
startPoint.original = centerIntersect;
}
function fixDockings(connection) {
var waypoints = connection.waypoints;
adjustDocking(
waypoints[0],
waypoints[1],
getMid(connection.source)
);
adjustDocking(
waypoints[waypoints.length - 1],
waypoints[waypoints.length - 2],
getMid(connection.target)
);
}
eventBus.on('bpmnElement.added', function(e) {
var element = e.element;
if (element.waypoints) {
fixDockings(element);
}
});
}
ImportDockingFix.$inject = [
'eventBus'
];
// helpers //////////////////////
function getDistance(p1, p2) {
return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2));
}
================================================
FILE: lib/features/modeling/behavior/IsHorizontalFix.js
================================================
import inherits from 'inherits-browser';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import {
getBusinessObject,
getDi
} from '../../../util/ModelUtil';
import {
isAny
} from '../util/ModelingUtil';
/**
* @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
*/
/**
* A component that makes sure that each created or updated
* Pool and Lane is assigned an isHorizontal property set to true.
*
* @param {EventBus} eventBus
*/
export default function IsHorizontalFix(eventBus) {
CommandInterceptor.call(this, eventBus);
var elementTypesToUpdate = [
'bpmn:Participant',
'bpmn:Lane'
];
this.executed([ 'shape.move', 'shape.create', 'shape.resize' ], function(event) {
var shape = event.context.shape,
bo = getBusinessObject(shape),
di = getDi(shape);
if (isAny(bo, elementTypesToUpdate)) {
var isHorizontal = di.get('isHorizontal');
if (isHorizontal === undefined) {
isHorizontal = true;
}
// set attribute directly to avoid modeling#updateProperty side effects
di.set('isHorizontal', isHorizontal);
}
});
}
IsHorizontalFix.$inject = [ 'eventBus' ];
inherits(IsHorizontalFix, CommandInterceptor);
================================================
FILE: lib/features/modeling/behavior/LabelBehavior.js
================================================
import {
assign
} from 'min-dash';
import inherits from 'inherits-browser';
import {
is,
getBusinessObject
} from '../../../util/ModelUtil';
import {
isLabelExternal,
getLabel,
hasExternalLabel,
isLabel
} from '../../../util/LabelUtil';
import {
getLabelAdjustment
} from './util/LabelLayoutUtil';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import {
getNewAttachPoint
} from 'diagram-js/lib/util/AttachUtil';
import {
getMid,
roundPoint
} from 'diagram-js/lib/layout/LayoutUtil';
import {
delta
} from 'diagram-js/lib/util/PositionUtil';
import {
sortBy
} from 'min-dash';
import {
getDistancePointLine,
perpendicularFoot
} from './util/GeometricUtil';
var NAME_PROPERTY = 'name';
var TEXT_PROPERTY = 'text';
/**
* @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
* @typedef {import('../Modeling').default} Modeling
* @typedef {import('../BpmnFactory').default} BpmnFactory
* @typedef {import('../../../draw/TextRenderer').default} TextRenderer
*
* @typedef {import('diagram-js/lib/util/Types').Point} Point
* @typedef {import('diagram-js/lib/util/Types').Rect} Rect
*
* @typedef {Point[]} Line
*/
/**
* A component that makes sure that external labels are added
* together with respective elements and properly updated (DI wise)
* during move.
*
* @param {EventBus} eventBus
* @param {Modeling} modeling
* @param {BpmnFactory} bpmnFactory
* @param {TextRenderer} textRenderer
*/
export default function LabelBehavior(
eventBus, modeling, bpmnFactory,
textRenderer) {
CommandInterceptor.call(this, eventBus);
// update label if name property was updated
this.postExecute('element.updateProperties', onPropertyUpdate);
this.postExecute('element.updateModdleProperties', e => {
const elementBo = getBusinessObject(e.context.element);
if (elementBo === e.context.moddleElement) {
onPropertyUpdate(e);
}
});
function onPropertyUpdate(e) {
var context = e.context,
element = context.element,
properties = context.properties;
if (NAME_PROPERTY in properties) {
modeling.updateLabel(element, properties[NAME_PROPERTY]);
}
if (TEXT_PROPERTY in properties
&& is(element, 'bpmn:TextAnnotation')) {
var newBounds = textRenderer.getTextAnnotationBounds(
{
x: element.x,
y: element.y,
width: element.width,
height: element.height
},
properties[TEXT_PROPERTY] || ''
);
modeling.updateLabel(element, properties.text, newBounds);
}
}
// create label shape after shape/connection was created
this.postExecute([ 'shape.create', 'connection.create' ], function(e) {
var context = e.context,
hints = context.hints || {};
if (hints.createElementsBehavior === false) {
return;
}
var element = context.shape || context.connection;
if (isLabel(element) || !isLabelExternal(element)) {
return;
}
// only create label if attribute available
if (!getLabel(element)) {
return;
}
modeling.updateLabel(element, getLabel(element));
});
// update label after label shape was deleted
this.postExecute('shape.delete', function(event) {
var context = event.context,
labelTarget = context.labelTarget,
hints = context.hints || {};
// check if label
if (labelTarget && hints.unsetLabel !== false) {
modeling.updateLabel(labelTarget, null, null, { removeShape: false });
}
});
function getVisibleLabelAdjustment(event) {
var context = event.context,
connection = context.connection,
label = connection.label,
hints = assign({}, context.hints),
newWaypoints = context.newWaypoints || connection.waypoints,
oldWaypoints = context.oldWaypoints;
if (typeof hints.startChanged === 'undefined') {
hints.startChanged = !!hints.connectionStart;
}
if (typeof hints.endChanged === 'undefined') {
hints.endChanged = !!hints.connectionEnd;
}
return getLabelAdjustment(label, newWaypoints, oldWaypoints, hints);
}
this.postExecute([
'connection.layout',
'connection.updateWaypoints'
], function(event) {
var context = event.context,
hints = context.hints || {};
if (hints.labelBehavior === false) {
return;
}
var connection = context.connection,
label = connection.label,
labelAdjustment;
// handle missing label as well as the case
// that the label parent does not exist (yet),
// because it is being pasted / created via multi element create
//
// Cf. https://github.com/bpmn-io/bpmn-js/pull/1227
if (!label || !label.parent) {
return;
}
labelAdjustment = getVisibleLabelAdjustment(event);
modeling.moveShape(label, labelAdjustment);
});
// keep label position on shape replace
this.postExecute([ 'shape.replace' ], function(event) {
var context = event.context,
newShape = context.newShape,
oldShape = context.oldShape;
var businessObject = getBusinessObject(newShape);
if (businessObject
&& isLabelExternal(businessObject)
&& oldShape.label
&& newShape.label) {
newShape.label.x = oldShape.label.x;
newShape.label.y = oldShape.label.y;
}
});
// 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 (hasExternalLabel(shape)) {
var label = shape.label,
labelMid = getMid(label),
edges = asEdges(oldBounds);
// get nearest border point to label as reference point
var referencePoint = getReferencePoint(labelMid, edges);
var delta = getReferencePointDelta(referencePoint, oldBounds, newBounds);
modeling.moveShape(label, delta);
}
});
}
inherits(LabelBehavior, CommandInterceptor);
LabelBehavior.$inject = [
'eventBus',
'modeling',
'bpmnFactory',
'textRenderer'
];
// helpers //////////////////////
/**
* Calculates a reference point delta relative to a new position
* of a certain element's bounds
*
* @param {Point} referencePoint
* @param {Rect} oldBounds
* @param {Rect} newBounds
*
* @return {Point}
*/
export function getReferencePointDelta(referencePoint, oldBounds, newBounds) {
var newReferencePoint = getNewAttachPoint(referencePoint, oldBounds, newBounds);
return roundPoint(delta(newReferencePoint, referencePoint));
}
/**
* Generates the nearest point (reference point) for a given point
* onto given set of lines
*
* @param {Point} point
* @param {Line[]} lines
*
* @return {Point}
*/
export function getReferencePoint(point, lines) {
if (!lines.length) {
return;
}
var nearestLine = getNearestLine(point, lines);
return perpendicularFoot(point, nearestLine);
}
/**
* Convert the given bounds to a lines array containing all edges
*
* @param {Rect|Point} bounds
*
* @return {Line[]}
*/
export function asEdges(bounds) {
return [
[ // top
{
x: bounds.x,
y: bounds.y
},
{
x: bounds.x + (bounds.width || 0),
y: bounds.y
}
],
[ // right
{
x: bounds.x + (bounds.width || 0),
y: bounds.y
},
{
x: bounds.x + (bounds.width || 0),
y: bounds.y + (bounds.height || 0)
}
],
[ // bottom
{
x: bounds.x,
y: bounds.y + (bounds.height || 0)
},
{
x: bounds.x + (bounds.width || 0),
y: bounds.y + (bounds.height || 0)
}
],
[ // left
{
x: bounds.x,
y: bounds.y
},
{
x: bounds.x,
y: bounds.y + (bounds.height || 0)
}
]
];
}
/**
* Returns the nearest line for a given point by distance
* @param {Point} point
* @param {Line[]} lines
*
* @return {Line}
*/
function getNearestLine(point, lines) {
var distances = lines.map(function(l) {
return {
line: l,
distance: getDistancePointLine(point, l)
};
});
var sorted = sortBy(distances, 'distance');
return sorted[0].line;
}
================================================
FILE: lib/features/modeling/behavior/LayoutConnectionBehavior.js
================================================
import {
assign
} from 'min-dash';
import inherits from 'inherits-browser';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import { getConnectionAdjustment as getConnectionAnchorPoint } from './util/ConnectionLayoutUtil';
/**
* @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
* @typedef {import('../Modeling').default} Modeling
*/
/**
* A component that makes sure that Associations connected to Connections
* are updated together with the Connection.
*
* @param {EventBus} eventBus
* @param {Modeling} modeling
*/
export default function LayoutConnectionBehavior(eventBus, modeling) {
CommandInterceptor.call(this, eventBus);
function getnewAnchorPoint(event, point) {
var context = event.context,
connection = context.connection,
hints = assign({}, context.hints),
newWaypoints = context.newWaypoints || connection.waypoints,
oldWaypoints = context.oldWaypoints;
if (typeof hints.startChanged === 'undefined') {
hints.startChanged = !!hints.connectionStart;
}
if (typeof hints.endChanged === 'undefined') {
hints.endChanged = !!hints.connectionEnd;
}
return getConnectionAnchorPoint(point, newWaypoints, oldWaypoints, hints);
}
this.postExecute([
'connection.layout',
'connection.updateWaypoints'
], function(event) {
var context = event.context;
var connection = context.connection,
outgoing = connection.outgoing,
incoming = connection.incoming;
incoming.forEach(function(connection) {
var endPoint = connection.waypoints[connection.waypoints.length - 1];
var newEndpoint = getnewAnchorPoint(event, endPoint);
var newWaypoints = [].concat(connection.waypoints.slice(0, -1), [ newEndpoint ]);
modeling.updateWaypoints(connection, newWaypoints);
});
outgoing.forEach(function(connection) {
var startpoint = connection.waypoints[0];
var newStartpoint = getnewAnchorPoint(event, startpoint);
var newWaypoints = [].concat([ newStartpoint ], connection.waypoints.slice(1));
modeling.updateWaypoints(connection, newWaypoints);
});
});
this.postExecute([
'connection.move'
], function(event) {
var context = event.context;
var connection = context.connection,
outgoing = connection.outgoing,
incoming = connection.incoming,
delta = context.delta;
incoming.forEach(function(connection) {
var endPoint = connection.waypoints[connection.waypoints.length - 1];
var newEndpoint = {
x: endPoint.x + delta.x,
y: endPoint.y + delta.y
};
var newWaypoints = [].concat(connection.waypoints.slice(0, -1), [ newEndpoint ]);
modeling.updateWaypoints(connection, newWaypoints);
});
outgoing.forEach(function(connection) {
var startpoint = connection.waypoints[0];
var newStartpoint = {
x: startpoint.x + delta.x,
y: startpoint.y + delta.y
};
var newWaypoints = [].concat([ newStartpoint ], connection.waypoints.slice(1));
modeling.updateWaypoints(connection, newWaypoints);
});
});
}
inherits(LayoutConnectionBehavior, CommandInterceptor);
LayoutConnectionBehavior.$inject = [
'eventBus',
'modeling'
];
================================================
FILE: lib/features/modeling/behavior/MessageFlowBehavior.js
================================================
import inherits from 'inherits-browser';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import { is } from '../../../util/ModelUtil';
import { isExpanded } from '../../../util/DiUtil';
import { selfAndAllChildren } from 'diagram-js/lib/util/Elements';
import {
getResizedSourceAnchor,
getResizedTargetAnchor
} from 'diagram-js/lib/features/modeling/cmd/helper/AnchorsHelper';
/**
* @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
* @typedef {import('../Modeling').default} Modeling
*/
/**
* BPMN-specific message flow behavior.
*
* @param {EventBus} eventBus
* @param {Modeling} modeling
*/
export default function MessageFlowBehavior(eventBus, modeling) {
CommandInterceptor.call(this, eventBus);
this.postExecute('shape.replace', function(context) {
var oldShape = context.oldShape,
newShape = context.newShape;
if (!isParticipantCollapse(oldShape, newShape)) {
return;
}
var messageFlows = getMessageFlows(oldShape);
messageFlows.incoming.forEach(function(incoming) {
var anchor = getResizedTargetAnchor(incoming, newShape, oldShape);
modeling.reconnectEnd(incoming, newShape, anchor);
});
messageFlows.outgoing.forEach(function(outgoing) {
var anchor = getResizedSourceAnchor(outgoing, newShape, oldShape);
modeling.reconnectStart(outgoing, newShape, anchor);
});
}, true);
}
MessageFlowBehavior.$inject = [ 'eventBus', 'modeling' ];
inherits(MessageFlowBehavior, CommandInterceptor);
// helpers //////////
function isParticipantCollapse(oldShape, newShape) {
return is(oldShape, 'bpmn:Participant')
&& isExpanded(oldShape)
&& is(newShape, 'bpmn:Participant')
&& !isExpanded(newShape);
}
function getMessageFlows(parent) {
var elements = selfAndAllChildren([ parent ], false);
var incoming = [],
outgoing = [];
elements.forEach(function(element) {
if (element === parent) {
return;
}
element.incoming.forEach(function(connection) {
if (is(connection, 'bpmn:MessageFlow')) {
incoming.push(connection);
}
});
element.outgoing.forEach(function(connection) {
if (is(connection, 'bpmn:MessageFlow')) {
outgoing.push(connection);
}
});
}, []);
return {
incoming: incoming,
outgoing: outgoing
};
}
================================================
FILE: lib/features/modeling/behavior/NonInterruptingBehavior.js
================================================
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import inherits from 'inherits-browser';
import { canBeNonInterrupting, getInterruptingProperty } from './util/NonInterruptingUtil';
import { getBusinessObject } from '../../../util/ModelUtil';
export default function NonInterruptingBehavior(injector, modeling) {
injector.invoke(CommandInterceptor, this);
this.postExecuted('shape.replace', function(event) {
const oldShape = event.context.oldShape;
const newShape = event.context.newShape;
const hints = event.context.hints;
if (!canBeNonInterrupting(newShape)) {
return;
}
const property = getInterruptingProperty(newShape);
const isExplicitChange = hints.targetElement && hints.targetElement[property] !== undefined;
if (isExplicitChange) {
return;
}
const isOldInterrupting = getBusinessObject(oldShape).get(property);
const isNewInterruptingDefault = getBusinessObject(newShape).get(property);
if (isOldInterrupting === isNewInterruptingDefault) {
return;
}
modeling.updateProperties(newShape, {
[property]: isOldInterrupting
});
});
}
NonInterruptingBehavior.$inject = [ 'injector', 'modeling' ];
inherits(NonInterruptingBehavior, CommandInterceptor);
================================================
FILE: lib/features/modeling/behavior/RemoveElementBehavior.js
================================================
import inherits from 'inherits-browser';
import { is } from '../../../util/ModelUtil';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import lineIntersect from './util/LineIntersect';
/**
* @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
* @typedef {import('../../rules/BpmnRules').default} BpmnRules
* @typedef {import('../Modeling').default} Modeling
*/
/**
* @param {EventBus} eventBus
* @param {BpmnRules} bpmnRules
* @param {Modeling} modeling
*/
export default function RemoveElementBehavior(eventBus, bpmnRules, modeling) {
CommandInterceptor.call(this, eventBus);
/**
* Combine sequence flows when deleting an element
* if there is one incoming and one outgoing
* sequence flow
*/
this.preExecute('shape.delete', function(e) {
var shape = e.context.shape;
// only handle [a] -> [shape] -> [b] patterns
if (shape.incoming.length !== 1 || shape.outgoing.length !== 1) {
return;
}
var inConnection = shape.incoming[0],
outConnection = shape.outgoing[0];
// only handle sequence flows
if (!is(inConnection, 'bpmn:SequenceFlow') || !is(outConnection, 'bpmn:SequenceFlow')) {
return;
}
if (bpmnRules.canConnect(inConnection.source, outConnection.target, inConnection)) {
// compute new, combined waypoints
var newWaypoints = getNewWaypoints(inConnection.waypoints, outConnection.waypoints);
modeling.reconnectEnd(inConnection, outConnection.target, newWaypoints);
}
});
}
inherits(RemoveElementBehavior, CommandInterceptor);
RemoveElementBehavior.$inject = [
'eventBus',
'bpmnRules',
'modeling'
];
// helpers //////////////////////
function getDocking(point) {
return point.original || point;
}
function getNewWaypoints(inWaypoints, outWaypoints) {
var intersection = lineIntersect(
getDocking(inWaypoints[inWaypoints.length - 2]),
getDocking(inWaypoints[inWaypoints.length - 1]),
getDocking(outWaypoints[1]),
getDocking(outWaypoints[0]));
if (intersection) {
return [].concat(
inWaypoints.slice(0, inWaypoints.length - 1),
[ intersection ],
outWaypoints.slice(1));
} else {
return [
getDocking(inWaypoints[0]),
getDocking(outWaypoints[outWaypoints.length - 1])
];
}
}
================================================
FILE: lib/features/modeling/behavior/RemoveEmbeddedLabelBoundsBehavior.js
================================================
import inherits from 'inherits-browser';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import { getDi } from '../../../util/ModelUtil';
/**
* @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
* @typedef {import('../Modeling').default} Modeling
*/
/**
* BPMN specific behavior ensuring that bpmndi:Label's dc:Bounds are removed
* when shape is resized.
*
* @param {EventBus} eventBus
* @param {Modeling} modeling
*/
export default function RemoveEmbeddedLabelBoundsBehavior(eventBus, modeling) {
CommandInterceptor.call(this, eventBus);
this.preExecute('shape.resize', function(context) {
var shape = context.shape;
var di = getDi(shape),
label = di && di.get('label'),
bounds = label && label.get('bounds');
if (bounds) {
modeling.updateModdleProperties(shape, label, {
bounds: undefined
});
}
}, true);
}
inherits(RemoveEmbeddedLabelBoundsBehavior, CommandInterceptor);
RemoveEmbeddedLabelBoundsBehavior.$inject = [
'eventBus',
'modeling'
];
================================================
FILE: lib/features/modeling/behavior/RemoveParticipantBehavior.js
================================================
import inherits from 'inherits-browser';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import { is } from '../../../util/ModelUtil';
/**
* @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
* @typedef {import('../Modeling').default} Modeling
*/
/**
* BPMN specific remove behavior.
*
* @param {EventBus} eventBus
* @param {Modeling} modeling
*/
export default function RemoveParticipantBehavior(eventBus, modeling) {
CommandInterceptor.call(this, eventBus);
/**
* morph collaboration diagram into process diagram
* after the last participant has been removed
*/
this.preExecute('shape.delete', function(context) {
var shape = context.shape,
parent = shape.parent;
// activate the behavior if the shape to be removed
// is a participant
if (is(shape, 'bpmn:Participant')) {
context.collaborationRoot = parent;
}
}, true);
this.postExecute('shape.delete', function(context) {
var collaborationRoot = context.collaborationRoot;
if (collaborationRoot && !collaborationRoot.businessObject.participants.length) {
// replace empty collaboration with process diagram
var process = modeling.makeProcess();
// move all root elements from collaboration to process
var children = collaborationRoot.children.slice();
modeling.moveElements(children, { x: 0, y: 0 }, process);
}
}, true);
}
RemoveParticipantBehavior.$inject = [ 'eventBus', 'modeling' ];
inherits(RemoveParticipantBehavior, CommandInterceptor);
================================================
FILE: lib/features/modeling/behavior/ReplaceConnectionBehavior.js
================================================
import {
forEach,
find,
matchPattern
} from 'min-dash';
import inherits from 'inherits-browser';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import { is } from '../../../util/ModelUtil';
/**
* @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
* @typedef {import('../Modeling').default} Modeling
* @typedef {import('../../rules/BpmnRules').default} BpmnRules
* @typedef {import('didi').Injector} Injector
*/
/**
* @param {EventBus} eventBus
* @param {Modeling} modeling
* @param {BpmnRules} bpmnRules
* @param {Injector} injector
*/
export default function ReplaceConnectionBehavior(eventBus, modeling, bpmnRules, injector) {
CommandInterceptor.call(this, eventBus);
var dragging = injector.get('dragging', false);
function fixConnection(connection) {
var source = connection.source,
target = connection.target,
parent = connection.parent;
// do not do anything if connection
// is already deleted (may happen due to other
// behaviors plugged-in before)
if (!parent) {
return;
}
var replacementType,
remove;
/**
* Check if incoming or outgoing connections
* can stay or could be substituted with an
* appropriate replacement.
*
* This holds true for SequenceFlow <> MessageFlow.
*/
if (is(connection, 'bpmn:SequenceFlow')) {
if (!bpmnRules.canConnectSequenceFlow(source, target)) {
remove = true;
}
if (bpmnRules.canConnectMessageFlow(source, target)) {
replacementType = 'bpmn:MessageFlow';
}
}
// transform message flows into sequence flows, if possible
if (is(connection, 'bpmn:MessageFlow')) {
if (!bpmnRules.canConnectMessageFlow(source, target)) {
remove = true;
}
if (bpmnRules.canConnectSequenceFlow(source, target)) {
replacementType = 'bpmn:SequenceFlow';
}
}
// remove invalid connection,
// unless it has been removed already
if (remove) {
modeling.removeConnection(connection);
}
// replace SequenceFlow <> MessageFlow
if (replacementType) {
modeling.connect(source, target, {
type: replacementType,
waypoints: connection.waypoints.slice()
});
}
}
function replaceReconnectedConnection(event) {
var context = event.context,
connection = context.connection,
source = context.newSource || connection.source,
target = context.newTarget || connection.target,
allowed,
replacement;
allowed = bpmnRules.canConnect(source, target);
if (!allowed || allowed.type === connection.type) {
return;
}
replacement = modeling.connect(source, target, {
type: allowed.type,
associationDirection: allowed.associationDirection,
waypoints: connection.waypoints.slice()
});
// remove old connection unless it's already removed
if (connection.parent) {
modeling.removeConnection(connection);
}
// replace connection in context to reconnect end/start
context.connection = replacement;
if (dragging) {
cleanDraggingSelection(connection, replacement);
}
}
// monkey-patch selection saved in dragging in order to re-select it when operation is finished
function cleanDraggingSelection(oldConnection, newConnection) {
var context = dragging.context(),
previousSelection = context && context.payload.previousSelection,
index;
// do nothing if not dragging or no selection was present
if (!previousSelection || !previousSelection.length) {
return;
}
index = previousSelection.indexOf(oldConnection);
if (index === -1) {
return;
}
previousSelection.splice(index, 1, newConnection);
}
// lifecycle hooks
this.postExecuted('elements.move', function(context) {
var closure = context.closure,
allConnections = closure.allConnections;
forEach(allConnections, fixConnection);
}, true);
this.preExecute('connection.reconnect', replaceReconnectedConnection);
this.postExecuted('element.updateProperties', function(event) {
var context = event.context,
properties = context.properties,
element = context.element,
businessObject = element.businessObject,
connection;
// remove condition on change to default
if (properties.default) {
connection = find(
element.outgoing,
matchPattern({ id: element.businessObject.default.id })
);
if (connection) {
modeling.updateProperties(connection, { conditionExpression: undefined });
}
}
// remove default from source on change to conditional
if (properties.conditionExpression && businessObject.sourceRef.default === businessObject) {
modeling.updateProperties(element.source, { default: undefined });
}
});
}
inherits(ReplaceConnectionBehavior, CommandInterceptor);
ReplaceConnectionBehavior.$inject = [
'eventBus',
'modeling',
'bpmnRules',
'injector'
];
================================================
FILE: lib/features/modeling/behavior/ReplaceElementBehaviour.js
================================================
import inherits from 'inherits-browser';
import { forEach, reduce } from 'min-dash';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import { isEventSubProcess } from '../../../util/DiUtil';
/**
* @typedef {import('../../replace/BpmnReplace').default} BpmnReplace
* @typedef {import('../../rules/BpmnRules').default} BpmnRules
* @typedef {import('diagram-js/lib/core/ElementRegistry').default} ElementRegistry
* @typedef {import('didi').Injector} Injector
* @typedef {import('../Modeling').default} Modeling
* @typedef {import('diagram-js/lib/features/selection/Selection').default} Selection
*/
/**
* BPMN-specific replace behavior.
*
* @param {BpmnReplace} bpmnReplace
* @param {BpmnRules} bpmnRules
* @param {ElementRegistry} elementRegistry
* @param {Injector} injector
* @param {Modeling} modeling
* @param {Selection} selection
*/
export default function ReplaceElementBehaviour(
bpmnReplace,
bpmnRules,
elementRegistry,
injector,
modeling,
selection
) {
injector.invoke(CommandInterceptor, this);
this._bpmnReplace = bpmnReplace;
this._elementRegistry = elementRegistry;
this._selection = selection;
// replace elements on create, e.g. during copy-paste
this.postExecuted([ 'elements.create' ], 500, function(event) {
var context = event.context,
target = context.parent,
elements = context.elements;
var elementReplacements = reduce(elements, function(replacements, element) {
var canReplace = bpmnRules.canReplace([ element ], element.host || element.parent || target);
return canReplace ? replacements.concat(canReplace.replacements) : replacements;
}, []);
if (elementReplacements.length) {
this._replaceElements(elements, elementReplacements);
}
}, this);
// replace elements on move
this.postExecuted([ 'elements.move' ], 500, function(event) {
var context = event.context,
target = context.newParent,
newHost = context.newHost,
elements = [];
forEach(context.closure.topLevel, function(topLevelElements) {
if (isEventSubProcess(topLevelElements)) {
elements = elements.concat(topLevelElements.children);
} else {
elements = elements.concat(topLevelElements);
}
});
// set target to host if attaching
if (elements.length === 1 && newHost) {
target = newHost;
}
var canReplace = bpmnRules.canReplace(elements, target);
if (canReplace) {
this._replaceElements(elements, canReplace.replacements, newHost);
}
}, this);
// update attachments on host replace
this.postExecute([ 'shape.replace' ], 1500, function(e) {
var context = e.context,
oldShape = context.oldShape,
newShape = context.newShape,
attachers = oldShape.attachers,
canReplace;
if (attachers && attachers.length) {
canReplace = bpmnRules.canReplace(attachers, newShape);
this._replaceElements(attachers, canReplace.replacements);
}
}, this);
// keep ID on shape replace
this.postExecuted([ 'shape.replace' ], 1500, function(e) {
var context = e.context,
oldShape = context.oldShape,
newShape = context.newShape;
modeling.unclaimId(oldShape.businessObject.id, oldShape.businessObject);
modeling.updateProperties(newShape, { id: oldShape.id });
});
}
inherits(ReplaceElementBehaviour, CommandInterceptor);
ReplaceElementBehaviour.prototype._replaceElements = function(elements, newElements) {
var elementRegistry = this._elementRegistry,
bpmnReplace = this._bpmnReplace,
selection = this._selection;
forEach(newElements, function(replacement) {
var newElement = {
type: replacement.newElementType
};
var oldElement = elementRegistry.get(replacement.oldElementId);
var idx = elements.indexOf(oldElement);
elements[idx] = bpmnReplace.replaceElement(oldElement, newElement, { select: false });
});
if (newElements) {
selection.select(elements);
}
};
ReplaceElementBehaviour.$inject = [
'bpmnReplace',
'bpmnRules',
'elementRegistry',
'injector',
'modeling',
'selection'
];
================================================
FILE: lib/features/modeling/behavior/ResizeBehavior.js
================================================
import { is } from '../../../util/ModelUtil';
import {
isExpanded,
isHorizontal
} from '../../../util/DiUtil';
import {
asTRBL
} from 'diagram-js/lib/layout/LayoutUtil';
import {
collectLanes,
getLanesRoot
} from '../util/LaneUtil';
var HIGH_PRIORITY = 1500;
/**
* @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
*
* @typedef {import('../../../model/Types').Shape} Shape
*
* @typedef {import('diagram-js/lib/util/Types').Dimensions} Dimensions
* @typedef {import('diagram-js/lib/util/Types').Direction} Direction
* @typedef {import('diagram-js/lib/util/Types').RectTRBL} RectTRBL
*/
/**
* @type {Dimensions}
*/
export var GROUP_MIN_DIMENSIONS = { width: 140, height: 120 };
/**
* @type {Dimensions}
*/
export var LANE_MIN_DIMENSIONS = { width: 300, height: 60 };
/**
* @type {Dimensions}
*/
export var VERTICAL_LANE_MIN_DIMENSIONS = { width: 60, height: 300 };
/**
* @type {Dimensions}
*/
export var PARTICIPANT_MIN_DIMENSIONS = { width: 300, height: 150 };
/**
* @type {Dimensions}
*/
export var VERTICAL_PARTICIPANT_MIN_DIMENSIONS = { width: 150, height: 300 };
/**
* @type {Dimensions}
*/
export var SUB_PROCESS_MIN_DIMENSIONS = { width: 140, height: 120 };
/**
* @type {Dimensions}
*/
export var TEXT_ANNOTATION_MIN_DIMENSIONS = { width: 50, height: 30 };
/**
* Set minimum bounds/resize constraints on resize.
*
* @param {EventBus} eventBus
*/
export default function ResizeBehavior(eventBus) {
eventBus.on('resize.start', HIGH_PRIORITY, function(event) {
var context = event.context,
shape = context.shape,
direction = context.direction,
balanced = context.balanced;
if (is(shape, 'bpmn:Lane') || is(shape, 'bpmn:Participant')) {
context.resizeConstraints = getParticipantResizeConstraints(shape, direction, balanced);
}
if (is(shape, 'bpmn:SubProcess') && isExpanded(shape)) {
context.minDimensions = SUB_PROCESS_MIN_DIMENSIONS;
}
if (is(shape, 'bpmn:TextAnnotation')) {
context.minDimensions = TEXT_ANNOTATION_MIN_DIMENSIONS;
}
});
}
ResizeBehavior.$inject = [ 'eventBus' ];
var abs = Math.abs,
min = Math.min,
max = Math.max;
function addToTrbl(trbl, attr, value, choice) {
var current = trbl[attr];
// make sure to set the value if it does not exist
// or apply the correct value by comparing against
// choice(value, currentValue)
trbl[attr] = current === undefined ? value : choice(value, current);
}
function addMin(trbl, attr, value) {
return addToTrbl(trbl, attr, value, min);
}
function addMax(trbl, attr, value) {
return addToTrbl(trbl, attr, value, max);
}
var LANE_PADDING = { top: 20, left: 50, right: 20, bottom: 20 },
VERTICAL_LANE_PADDING = { top: 50, left: 20, right: 20, bottom: 20 };
/**
* @param {Shape} laneShape
* @param {Direction} resizeDirection
* @param {boolean} [balanced=false]
*
* @return { {
* min: RectTRBL;
* max: RectTRBL;
* } }
*/
export function getParticipantResizeConstraints(laneShape, resizeDirection, balanced) {
var lanesRoot = getLanesRoot(laneShape);
var isFirst = true,
isLast = true;
var allLanes = collectLanes(lanesRoot, [ lanesRoot ]);
var laneTrbl = asTRBL(laneShape);
var maxTrbl = {},
minTrbl = {};
var isHorizontalLane = isHorizontal(laneShape);
var minDimensions = isHorizontalLane ? LANE_MIN_DIMENSIONS : VERTICAL_LANE_MIN_DIMENSIONS;
if (/n/.test(resizeDirection)) {
minTrbl.top = laneTrbl.bottom - minDimensions.height;
} else if (/e/.test(resizeDirection)) {
minTrbl.right = laneTrbl.left + minDimensions.width;
} else if (/s/.test(resizeDirection)) {
minTrbl.bottom = laneTrbl.top + minDimensions.height;
} else if (/w/.test(resizeDirection)) {
minTrbl.left = laneTrbl.right - minDimensions.width;
}
// min/max size based on related lanes
allLanes.forEach(function(other) {
var otherTrbl = asTRBL(other);
// lane flags
if (isHorizontalLane) {
if (otherTrbl.top < (laneTrbl.top - 10)) {
isFirst = false;
}
if (otherTrbl.bottom > (laneTrbl.bottom + 10)) {
isLast = false;
}
}
else {
if (otherTrbl.left < (laneTrbl.left - 10)) {
isFirst = false;
}
if (otherTrbl.right > (laneTrbl.right + 10)) {
isLast = false;
}
}
if (/n/.test(resizeDirection)) {
// max top size (based on next element)
if (balanced && abs(laneTrbl.top - otherTrbl.bottom) < 10) {
addMax(maxTrbl, 'top', otherTrbl.top + minDimensions.height);
}
// min top size (based on self or nested element)
if (abs(laneTrbl.top - otherTrbl.top) < 5) {
addMin(minTrbl, 'top', otherTrbl.bottom - minDimensions.height);
}
}
if (/e/.test(resizeDirection)) {
// max right size (based on previous element)
if (balanced && abs(laneTrbl.right - otherTrbl.left) < 10) {
addMin(maxTrbl, 'right', otherTrbl.right - minDimensions.width);
}
// min right size (based on self or nested element)
if (abs(laneTrbl.right - otherTrbl.right) < 5) {
addMax(minTrbl, 'right', otherTrbl.left + minDimensions.width);
}
}
if (/s/.test(resizeDirection)) {
// max bottom size (based on previous element)
if (balanced && abs(laneTrbl.bottom - otherTrbl.top) < 10) {
addMin(maxTrbl, 'bottom', otherTrbl.bottom - minDimensions.height);
}
// min bottom size (based on self or nested element)
if (abs(laneTrbl.bottom - otherTrbl.bottom) < 5) {
addMax(minTrbl, 'bottom', otherTrbl.top + minDimensions.height);
}
}
if (/w/.test(resizeDirection)) {
// max left size (based on next element)
if (balanced && abs(laneTrbl.left - otherTrbl.right) < 10) {
addMax(maxTrbl, 'left', otherTrbl.left + minDimensions.width);
}
// min left size (based on self or nested element)
if (abs(laneTrbl.left - otherTrbl.left) < 5) {
addMin(minTrbl, 'left', otherTrbl.right - minDimensions.width);
}
}
});
// max top/bottom/left/right size based on flow nodes
var flowElements = lanesRoot.children.filter(function(s) {
return !s.hidden && !s.waypoints && (is(s, 'bpmn:FlowElement') || is(s, 'bpmn:Artifact'));
});
var padding = isHorizontalLane ? LANE_PADDING : VERTICAL_LANE_PADDING;
flowElements.forEach(function(flowElement) {
var flowElementTrbl = asTRBL(flowElement);
// vertical lane will resize from top with respect to flow element irrespective of first or last lane
if (/n/.test(resizeDirection) && (!isHorizontalLane || isFirst)) {
addMin(minTrbl, 'top', flowElementTrbl.top - padding.top);
}
// horizonal lane will resize from right with respect to flow element irrespective of first or last lane
if (/e/.test(resizeDirection) && (isHorizontalLane || isLast)) {
addMax(minTrbl, 'right', flowElementTrbl.right + padding.right);
}
// vertical lane will resize from bottom with respect to flow element irrespective of first or last lane
if (/s/.test(resizeDirection) && (!isHorizontalLane || isLast)) {
addMax(minTrbl, 'bottom', flowElementTrbl.bottom + padding.bottom);
}
// horizonal lane will resize from left with respect to flow element irrespective of first or last lane
if (/w/.test(resizeDirection) && (isHorizontalLane || isFirst)) {
addMin(minTrbl, 'left', flowElementTrbl.left - padding.left);
}
});
return {
min: minTrbl,
max: maxTrbl
};
}
================================================
FILE: lib/features/modeling/behavior/ResizeLaneBehavior.js
================================================
import { is } from '../../../util/ModelUtil';
import {
roundBounds
} from 'diagram-js/lib/layout/LayoutUtil';
import {
hasPrimaryModifier
} from 'diagram-js/lib/util/Mouse';
/**
* @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
* @typedef {import('../Modeling').default} Modeling
*/
var SLIGHTLY_HIGHER_PRIORITY = 1001;
/**
* Invoke {@link Modeling#resizeLane} instead of {@link Modeling#resizeShape}
* when resizing a lane or participant shape.
*
* @param {EventBus} eventBus
* @param {Modeling} modeling
*/
export default function ResizeLaneBehavior(eventBus, modeling) {
eventBus.on('resize.start', SLIGHTLY_HIGHER_PRIORITY + 500, function(event) {
var context = event.context,
shape = context.shape;
if (is(shape, 'bpmn:Lane') || is(shape, 'bpmn:Participant')) {
// should we resize the opposite lane(s) in
// order to compensate for the resize operation?
context.balanced = !hasPrimaryModifier(event);
}
});
/**
* Intercept resize end and call resize lane function instead.
*/
eventBus.on('resize.end', SLIGHTLY_HIGHER_PRIORITY, function(event) {
var context = event.context,
shape = context.shape,
canExecute = context.canExecute,
newBounds = context.newBounds;
if (is(shape, 'bpmn:Lane') || is(shape, 'bpmn:Participant')) {
if (canExecute) {
// ensure we have actual pixel values for new bounds
// (important when zoom level was > 1 during move)
newBounds = roundBounds(newBounds);
// perform the actual resize
modeling.resizeLane(shape, newBounds, context.balanced);
}
// stop propagation
return false;
}
});
}
ResizeLaneBehavior.$inject = [
'eventBus',
'modeling'
];
================================================
FILE: lib/features/modeling/behavior/RootElementReferenceBehavior.js
================================================
import inherits from 'inherits-browser';
import {
find,
isArray,
matchPattern,
some
} from 'min-dash';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import {
add as collectionAdd,
remove as collectionRemove
} from 'diagram-js/lib/util/Collections';
import {
getBusinessObject,
is
} from '../../../util/ModelUtil';
import { isAny } from '../util/ModelingUtil';
import { hasEventDefinition } from '../../../util/DiUtil';
/**
* @typedef {import('../../../Modeler').default} Modeler
* @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
* @typedef {import('didi').Injector} Injector
* @typedef {import('../../copy-paste/ModdleCopy').default} ModdleCopy
* @typedef {import('../BpmnFactory').default} BpmnFactory
*
* @typedef {import('../../../model/Types').Element} Element
* @typedef {import('../../../model/Types').Shape} Shape
*
* @typedef {import('diagram-js/lib/util/Types').DirectionTRBL} DirectionTRBL
*/
var LOW_PRIORITY = 500;
/**
* Add referenced root elements (error, escalation, message, signal) if they don't exist.
* Copy referenced root elements on copy & paste.
*
* @param {Modeler} bpmnjs
* @param {EventBus} eventBus
* @param {Injector} injector
* @param {ModdleCopy} moddleCopy
* @param {BpmnFactory} bpmnFactory
*/
export default function RootElementReferenceBehavior(
bpmnjs, eventBus, injector, moddleCopy, bpmnFactory
) {
injector.invoke(CommandInterceptor, this);
function canHaveRootElementReference(element) {
return isAny(element, [ 'bpmn:ReceiveTask', 'bpmn:SendTask' ]) ||
hasAnyEventDefinition(element, [
'bpmn:ErrorEventDefinition',
'bpmn:EscalationEventDefinition',
'bpmn:MessageEventDefinition',
'bpmn:SignalEventDefinition'
]);
}
function hasRootElement(rootElement) {
var definitions = bpmnjs.getDefinitions(),
rootElements = definitions.get('rootElements');
return !!find(rootElements, matchPattern({ id: rootElement.id }));
}
function getRootElementReferencePropertyName(eventDefinition) {
if (is(eventDefinition, 'bpmn:ErrorEventDefinition')) {
return 'errorRef';
} else if (is(eventDefinition, 'bpmn:EscalationEventDefinition')) {
return 'escalationRef';
} else if (is(eventDefinition, 'bpmn:MessageEventDefinition')) {
return 'messageRef';
} else if (is(eventDefinition, 'bpmn:SignalEventDefinition')) {
return 'signalRef';
}
}
function getRootElement(businessObject) {
if (isAny(businessObject, [ 'bpmn:ReceiveTask', 'bpmn:SendTask' ])) {
return businessObject.get('messageRef');
}
var eventDefinitions = businessObject.get('eventDefinitions'),
eventDefinition = eventDefinitions[ 0 ];
return eventDefinition.get(getRootElementReferencePropertyName(eventDefinition));
}
function setRootElement(businessObject, rootElement) {
if (isAny(businessObject, [ 'bpmn:ReceiveTask', 'bpmn:SendTask' ])) {
return businessObject.set('messageRef', rootElement);
}
var eventDefinitions = businessObject.get('eventDefinitions'),
eventDefinition = eventDefinitions[ 0 ];
return eventDefinition.set(getRootElementReferencePropertyName(eventDefinition), rootElement);
}
// create shape
this.executed([
'shape.create',
'element.updateProperties',
'element.updateModdleProperties'
], function(context) {
var shape = context.shape || context.element;
if (!canHaveRootElementReference(shape)) {
return;
}
var businessObject = getBusinessObject(shape),
rootElement = getRootElement(businessObject),
rootElements;
if (rootElement && !hasRootElement(rootElement)) {
rootElements = bpmnjs.getDefinitions().get('rootElements');
// add root element
collectionAdd(rootElements, rootElement);
context.addedRootElement = rootElement;
}
}, true);
this.reverted([
'shape.create',
'element.updateProperties',
'element.updateModdleProperties'
], function(context) {
var addedRootElement = context.addedRootElement;
if (!addedRootElement) {
return;
}
var rootElements = bpmnjs.getDefinitions().get('rootElements');
// remove root element
collectionRemove(rootElements, addedRootElement);
}, true);
eventBus.on('copyPaste.copyElement', function(context) {
var descriptor = context.descriptor,
element = context.element;
if (element.labelTarget || !canHaveRootElementReference(element)) {
return;
}
var businessObject = getBusinessObject(element),
rootElement = getRootElement(businessObject);
if (rootElement) {
// TODO(nikku): clone on copy
descriptor.referencedRootElement = rootElement;
}
});
eventBus.on('copyPaste.pasteElement', LOW_PRIORITY, function(context) {
var descriptor = context.descriptor,
businessObject = descriptor.businessObject,
referencedRootElement = descriptor.referencedRootElement;
if (!referencedRootElement) {
return;
}
if (!hasRootElement(referencedRootElement)) {
referencedRootElement = moddleCopy.copyElement(
referencedRootElement,
bpmnFactory.create(referencedRootElement.$type)
);
}
setRootElement(businessObject, referencedRootElement);
delete descriptor.referencedRootElement;
});
}
RootElementReferenceBehavior.$inject = [
'bpmnjs',
'eventBus',
'injector',
'moddleCopy',
'bpmnFactory'
];
inherits(RootElementReferenceBehavior, CommandInterceptor);
// helpers //////////
function hasAnyEventDefinition(element, types) {
if (!isArray(types)) {
types = [ types ];
}
return some(types, function(type) {
return hasEventDefinition(element, type);
});
}
================================================
FILE: lib/features/modeling/behavior/SetCompensationActivityAfterPasteBehavior.js
================================================
import inherits from 'inherits-browser';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import { getBusinessObject, is } from '../../../util/ModelUtil';
import { hasEventDefinition } from '../../../util/DiUtil';
/**
* @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
* @typedef {import('../../rules/BpmnRules').default} BpmnRules
* @typedef {import('../Modeling').default} Modeling
*/
/**
* A behavior that sets the property of Compensation Activity after paste operation
*
* @param {EventBus} eventBus
* @param {Modeling} modeling
*/
export default function SetCompensationActivityAfterPasteBehavior(eventBus, modeling) {
CommandInterceptor.call(this, eventBus);
this.postExecuted('elements.create', function(event) {
const context = event.context,
elements = context.elements;
// check if compensation activity is connected to compensation boundary event
for (const element of elements) {
if (isForCompensation(element) && !isConnectedToCompensationBoundaryEvent(element)) {
modeling.updateProperties(element, { isForCompensation: undefined });
}
}
});
}
inherits(SetCompensationActivityAfterPasteBehavior, CommandInterceptor);
SetCompensationActivityAfterPasteBehavior.$inject = [
'eventBus',
'modeling'
];
// helpers //////////////////////
function isForCompensation(element) {
const bo = getBusinessObject(element);
return bo && bo.isForCompensation;
}
function isCompensationBoundaryEvent(element) {
return element && is(element, 'bpmn:BoundaryEvent') &&
hasEventDefinition(element, 'bpmn:CompensateEventDefinition');
}
function isConnectedToCompensationBoundaryEvent(element) {
const compensationAssociations = element.incoming.filter(
connection => isCompensationBoundaryEvent(connection.source)
);
if (compensationAssociations.length > 0) {
return true;
}
return false;
}
================================================
FILE: lib/features/modeling/behavior/SpaceToolBehavior.js
================================================
import { forEach } from 'min-dash';
import { is } from '../../../util/ModelUtil';
import {
isExpanded,
isHorizontal
} from '../../../util/DiUtil';
import {
GROUP_MIN_DIMENSIONS,
LANE_MIN_DIMENSIONS,
VERTICAL_LANE_MIN_DIMENSIONS,
PARTICIPANT_MIN_DIMENSIONS,
VERTICAL_PARTICIPANT_MIN_DIMENSIONS,
SUB_PROCESS_MIN_DIMENSIONS,
TEXT_ANNOTATION_MIN_DIMENSIONS
} from './ResizeBehavior';
import { getChildLanes } from '../util/LaneUtil';
/**
* @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
*
* @typedef {import('../../../model/Types').Shape} Shape
*/
var max = Math.max;
/**
* @param {EventBus} eventBus
*/
export default function SpaceToolBehavior(eventBus) {
eventBus.on('spaceTool.getMinDimensions', function(context) {
var shapes = context.shapes,
axis = context.axis,
start = context.start,
minDimensions = {};
forEach(shapes, function(shape) {
var id = shape.id;
if (is(shape, 'bpmn:Participant')) {
minDimensions[ id ] = getParticipantMinDimensions(shape, axis, start);
}
if (is(shape, 'bpmn:Lane')) {
minDimensions[ id ] = isHorizontal(shape) ? LANE_MIN_DIMENSIONS : VERTICAL_LANE_MIN_DIMENSIONS;
}
if (is(shape, 'bpmn:SubProcess') && isExpanded(shape)) {
minDimensions[ id ] = SUB_PROCESS_MIN_DIMENSIONS;
}
if (is(shape, 'bpmn:TextAnnotation')) {
minDimensions[ id ] = TEXT_ANNOTATION_MIN_DIMENSIONS;
}
if (is(shape, 'bpmn:Group')) {
minDimensions[ id ] = GROUP_MIN_DIMENSIONS;
}
});
return minDimensions;
});
}
SpaceToolBehavior.$inject = [ 'eventBus' ];
// helpers //////////
function isHorizontalAxis(axis) {
return axis === 'x';
}
/**
* Get minimum dimensions for participant taking lanes into account.
*
* @param {Shape} participant
* @param {Axis} axis
* @param {number} start
*
* @return {number}
*/
function getParticipantMinDimensions(participant, axis, start) {
var isHorizontalLane = isHorizontal(participant);
if (!hasChildLanes(participant)) {
return isHorizontalLane ? PARTICIPANT_MIN_DIMENSIONS : VERTICAL_PARTICIPANT_MIN_DIMENSIONS;
}
var isHorizontalResize = isHorizontalAxis(axis);
var minDimensions = {};
if (isHorizontalResize) {
if (isHorizontalLane) {
minDimensions = PARTICIPANT_MIN_DIMENSIONS;
} else {
minDimensions = {
width: getParticipantMinWidth(participant, start, isHorizontalResize),
height: VERTICAL_PARTICIPANT_MIN_DIMENSIONS.height
};
}
} else {
if (isHorizontalLane) {
minDimensions = {
width: PARTICIPANT_MIN_DIMENSIONS.width,
height: getParticipantMinHeight(participant, start, isHorizontalResize)
};
} else {
minDimensions = VERTICAL_PARTICIPANT_MIN_DIMENSIONS;
}
}
return minDimensions;
}
/**
* Get minimum height for participant taking lanes into account.
*
* @param {Shape} participant
* @param {number} start
* @param {boolean} isHorizontalResize
*
* @return {number}
*/
function getParticipantMinHeight(participant, start, isHorizontalResize) {
var lanesMinHeight;
lanesMinHeight = getLanesMinHeight(participant, start, isHorizontalResize);
return max(PARTICIPANT_MIN_DIMENSIONS.height, lanesMinHeight);
}
/**
* Get minimum width for participant taking lanes into account.
*
* @param {Shape} participant
* @param {number} start
* @param {boolean} isHorizontalResize
*
* @return {number}
*/
function getParticipantMinWidth(participant, start, isHorizontalResize) {
var lanesMinWidth;
lanesMinWidth = getLanesMinWidth(participant, start, isHorizontalResize);
return max(VERTICAL_PARTICIPANT_MIN_DIMENSIONS.width, lanesMinWidth);
}
function hasChildLanes(element) {
return !!getChildLanes(element).length;
}
function getLanesMinHeight(participant, resizeStart, isHorizontalResize) {
var lanes = getChildLanes(participant),
resizedLane;
// find the nested lane which is currently resized
resizedLane = findResizedLane(lanes, resizeStart, isHorizontalResize);
// resized lane cannot shrink below the minimum height
// but remaining lanes' dimensions are kept intact
return participant.height - resizedLane.height + LANE_MIN_DIMENSIONS.height;
}
function getLanesMinWidth(participant, resizeStart, isHorizontalResize) {
var lanes = getChildLanes(participant),
resizedLane;
// find the nested lane which is currently resized
resizedLane = findResizedLane(lanes, resizeStart, isHorizontalResize);
// resized lane cannot shrink below the minimum width
// but remaining lanes' dimensions are kept intact
return participant.width - resizedLane.width + VERTICAL_LANE_MIN_DIMENSIONS.width;
}
/**
* Find nested lane which is currently resized.
*
* @param {Shape[]} lanes
* @param {number} resizeStart
* @param {boolean} isHorizontalResize
*
* @return {Shape}
*/
function findResizedLane(lanes, resizeStart, isHorizontalResize) {
var i, lane, childLanes;
for (i = 0; i < lanes.length; i++) {
lane = lanes[i];
// resizing current lane or a lane nested
if (!isHorizontalResize && resizeStart >= lane.y && resizeStart <= lane.y + lane.height ||
isHorizontalResize && resizeStart >= lane.x && resizeStart <= lane.x + lane.width) {
childLanes = getChildLanes(lane);
// a nested lane is resized
if (childLanes.length) {
return findResizedLane(childLanes, resizeStart, isHorizontalResize);
}
// current lane is the resized one
return lane;
}
}
}
================================================
FILE: lib/features/modeling/behavior/SubProcessPlaneBehavior.js
================================================
import inherits from 'inherits-browser';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import { find, forEach } from 'min-dash';
import { isExpanded } from '../../../util/DiUtil';
import {
collectElementsAnnotations,
getElementAnnotations
} from '../../../util/AnnotationUtil';
import { getBusinessObject, getDi, is } from '../../../util/ModelUtil';
import { getMid } from 'diagram-js/lib/layout/LayoutUtil';
import { getBBox, selfAndChildren } from 'diagram-js/lib/util/Elements';
import {
getPlaneIdFromShape,
getShapeIdFromPlane,
isPlane,
toPlaneId
} from '../../../util/DrilldownUtil';
/**
* @typedef {import('diagram-js/lib/core/Canvas').default} Canvas
* @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
* @typedef {import('../Modeling').default} Modeling
* @typedef {import('../ElementFactory').default} ElementFactory
* @typedef {import('../BpmnFactory').default} BpmnFactory
* @typedef {import('../../../Modeler').default} Modeler
* @typedef {import('diagram-js/lib/core/ElementRegistry').default} ElementRegistry
*
* @typedef {import('../../../model/Types').Element} Element
* @typedef {import('../../../model/Types').Root} Root
* @typedef {import('../../../model/Types').ModdleElement} ModdleElement
*/
var LOW_PRIORITY = 400;
var HIGH_PRIORITY = 600;
var DEFAULT_POSITION = {
x: 180,
y: 160
};
/**
* Creates bpmndi:BPMNPlane elements and canvas planes when collapsed subprocesses are created.
*
* @param {Canvas} canvas
* @param {EventBus} eventBus
* @param {Modeling} modeling
* @param {ElementFactory} elementFactory
* @param {BpmnFactory} bpmnFactory
* @param {Modeler} bpmnjs
* @param {ElementRegistry} elementRegistry
*/
export default function SubProcessPlaneBehavior(
canvas, eventBus, modeling,
elementFactory, bpmnFactory, bpmnjs, elementRegistry) {
CommandInterceptor.call(this, eventBus);
this._canvas = canvas;
this._eventBus = eventBus;
this._modeling = modeling;
this._elementFactory = elementFactory;
this._bpmnFactory = bpmnFactory;
this._bpmnjs = bpmnjs;
this._elementRegistry = elementRegistry;
var self = this;
function isCollapsedSubProcess(element) {
return is(element, 'bpmn:SubProcess') && !isExpanded(element);
}
function createRoot(context) {
var shape = context.shape,
rootElement = context.newRootElement;
var businessObject = getBusinessObject(shape);
rootElement = self._addDiagram(rootElement || businessObject);
context.newRootElement = canvas.addRootElement(rootElement);
}
function removeRoot(context) {
var shape = context.shape;
var businessObject = getBusinessObject(shape);
self._removeDiagram(businessObject);
var rootElement = context.newRootElement = elementRegistry.get(getPlaneIdFromShape(businessObject));
canvas.removeRootElement(rootElement);
}
// add plane elements for newly created sub-processes
// this ensures we can actually drill down into the element
this.executed('shape.create', function(context) {
var shape = context.shape;
if (!isCollapsedSubProcess(shape)) {
return;
}
createRoot(context);
}, true);
this.postExecuted('elements.create', function(context) {
var elements = context.elements;
forEach(elements, function(element) {
if (!isCollapsedSubProcess(element)) {
return;
}
var rootElement = elementRegistry.get(getPlaneIdFromShape(element));
if (!rootElement || !element.children || !element.children.length) {
return;
}
var children = getSubProcessChildren(element);
self._showRecursively(children);
self._moveChildrenToShape(children, rootElement);
});
}, true);
this.reverted('shape.create', function(context) {
var shape = context.shape;
if (!isCollapsedSubProcess(shape)) {
return;
}
removeRoot(context);
}, true);
// remove annotations connected to elements inside the subprocess
this.preExecute('shape.delete', function(context) {
var shape = context.shape;
if (!is(shape, 'bpmn:SubProcess') || !isExpanded(shape)) {
return;
}
forEach(collectElementsAnnotations([ shape ]), (entry) => {
modeling.removeShape(entry.annotation);
});
}, true);
this.preExecuted('shape.delete', function(context) {
var shape = context.shape;
if (!isCollapsedSubProcess(shape)) {
return;
}
var attachedRoot = elementRegistry.get(getPlaneIdFromShape(shape));
if (!attachedRoot) {
return;
}
modeling.removeElements(attachedRoot.children.slice());
}, true);
this.executed('shape.delete', function(context) {
var shape = context.shape;
if (!isCollapsedSubProcess(shape)) {
return;
}
removeRoot(context);
}, true);
this.reverted('shape.delete', function(context) {
var shape = context.shape;
if (!isCollapsedSubProcess(shape)) {
return;
}
createRoot(context);
}, true);
this.preExecuted('shape.replace', function(context) {
var oldShape = context.oldShape;
var newShape = context.newShape;
if (!isCollapsedSubProcess(oldShape) || !isCollapsedSubProcess(newShape)) {
return;
}
// old plane could have content,
// we remove it so it is not recursively deleted from 'shape.delete'
context.oldRoot = canvas.removeRootElement(getPlaneIdFromShape(oldShape));
}, true);
this.postExecuted('shape.replace', function(context) {
var newShape = context.newShape,
source = context.oldRoot,
target = canvas.findRoot(getPlaneIdFromShape(newShape));
if (!source || !target) {
return;
}
var elements = source.children;
modeling.moveElements(elements, { x: 0, y: 0 }, target);
}, true);
// rename primary elements when the secondary element changes
// this ensures rootElement.id = element.id + '_plane'
this.executed('element.updateProperties', function(context) {
var shape = context.element;
if (!is(shape, 'bpmn:SubProcess')) {
return;
}
var properties = context.properties;
var oldProperties = context.oldProperties;
var oldId = oldProperties.id,
newId = properties.id;
if (oldId === newId) {
return;
}
if (isPlane(shape)) {
elementRegistry.updateId(shape, toPlaneId(newId));
elementRegistry.updateId(oldId, newId);
return;
}
var planeElement = elementRegistry.get(toPlaneId(oldId));
if (!planeElement) {
return;
}
elementRegistry.updateId(toPlaneId(oldId), toPlaneId(newId));
}, true);
this.reverted('element.updateProperties', function(context) {
var shape = context.element;
if (!is(shape, 'bpmn:SubProcess')) {
return;
}
var properties = context.properties;
var oldProperties = context.oldProperties;
var oldId = oldProperties.id,
newId = properties.id;
if (oldId === newId) {
return;
}
if (isPlane(shape)) {
elementRegistry.updateId(shape, toPlaneId(oldId));
elementRegistry.updateId(newId, oldId);
return;
}
var planeElement = elementRegistry.get(toPlaneId(newId));
if (!planeElement) {
return;
}
elementRegistry.updateId(planeElement, toPlaneId(oldId));
}, true);
// re-throw element.changed to re-render primary shape if associated plane has
// changed (e.g. bpmn:name property has changed)
eventBus.on('element.changed', function(context) {
var element = context.element;
if (!isPlane(element)) {
return;
}
var plane = element;
var primaryShape = elementRegistry.get(getShapeIdFromPlane(plane));
// do not re-throw if no associated primary shape (e.g. bpmn:Process)
if (!primaryShape || primaryShape === plane) {
return;
}
eventBus.fire('element.changed', { element: primaryShape });
});
// create/remove plane for the subprocess
this.executed('shape.toggleCollapse', LOW_PRIORITY, function(context) {
var shape = context.shape;
if (!is(shape, 'bpmn:SubProcess')) {
return;
}
if (!isExpanded(shape)) {
createRoot(context);
self._showRecursively(shape.children);
} else {
removeRoot(context);
}
}, true);
// create/remove plane for the subprocess
this.reverted('shape.toggleCollapse', LOW_PRIORITY, function(context) {
var shape = context.shape;
if (!is(shape, 'bpmn:SubProcess')) {
return;
}
if (!isExpanded(shape)) {
createRoot(context);
self._showRecursively(shape.children);
} else {
removeRoot(context);
}
}, true);
// move elements between planes
this.postExecuted('shape.toggleCollapse', HIGH_PRIORITY, function(context) {
var shape = context.shape;
if (!is(shape, 'bpmn:SubProcess')) {
return;
}
var rootElement = context.newRootElement;
if (!rootElement) {
return;
}
if (!isExpanded(shape)) {
// if an annotation is attached to both the sub-process and elements inside it,
// remove the inner associations, keeping only connections at the sub-process level,
// since we can't keep both after collapsing
self._disconnectSharedAnnotations(shape);
var children = getSubProcessChildren(shape);
self._moveChildrenToShape(children, rootElement);
} else {
self._moveChildrenToShape(rootElement.children.slice(), shape);
// annotations live at process level by design;
// move them back from the sub-process to the process root
forEach(collectElementsAnnotations(shape.children), (entry) => {
modeling.moveShape(entry.annotation, { x: 0, y: 0 }, shape.parent);
forEach(entry.associations, (association) => {
modeling.moveConnection(association, { x: 0, y: 0 }, shape.parent);
});
});
}
}, true);
// copy-paste ///////////
// add elements in plane to tree
eventBus.on('copyPaste.createTree', function(context) {
var element = context.element,
children = context.children;
if (!isCollapsedSubProcess(element)) {
return;
}
var id = getPlaneIdFromShape(element);
var parent = elementRegistry.get(id);
if (parent) {
// do not copy invisible root element
children.push.apply(children, parent.children);
}
});
// set plane children as direct children of collapsed shape
eventBus.on('copyPaste.copyElement', function(context) {
var descriptor = context.descriptor,
element = context.element,
elements = context.elements;
var parent = element.parent;
var isPlane = is(getDi(parent), 'bpmndi:BPMNPlane');
if (!isPlane) {
return;
}
var parentId = getShapeIdFromPlane(parent);
var referencedShape = find(elements, function(element) {
return element.id === parentId;
});
if (!referencedShape) {
return;
}
descriptor.parent = referencedShape.id;
});
// hide children during pasting
eventBus.on('copyPaste.pasteElement', function(context) {
var descriptor = context.descriptor;
if (!descriptor.parent) {
return;
}
if (isCollapsedSubProcess(descriptor.parent) || descriptor.parent.hidden) {
descriptor.hidden = true;
}
});
}
inherits(SubProcessPlaneBehavior, CommandInterceptor);
/**
* Moves the given elements to the target.
*
* If the target is a plane, the children are moved to the top left corner.
* Otherwise, the center of the target is used.
*
* @param {Element[]} children
* @param {Root} target
*/
SubProcessPlaneBehavior.prototype._moveChildrenToShape = function(children, target) {
var modeling = this._modeling;
if (!children.length) {
return;
}
// only change plane if there are no visible children, but don't move them
var visibleChildren = children.filter(function(child) {
return !child.hidden;
});
if (!visibleChildren.length) {
modeling.moveElements(children, { x: 0, y: 0 }, target, { autoResize: false });
return;
}
var childrenBounds = getBBox(visibleChildren);
var offset;
// target is a plane
if (!target.x) {
offset = {
x: DEFAULT_POSITION.x - childrenBounds.x,
y: DEFAULT_POSITION.y - childrenBounds.y
};
}
// source is a plane
else {
// move relative to the center of the shape
var targetMid = getMid(target);
var childrenMid = getMid(childrenBounds);
offset = {
x: targetMid.x - childrenMid.x,
y: targetMid.y - childrenMid.y
};
}
modeling.moveElements(children, offset, target, { autoResize: false });
};
/**
* Remove all associations between inner elements and annotations that are
* also connected to the sub-process itself.
*
* @param {Element} shape
*/
SubProcessPlaneBehavior.prototype._disconnectSharedAnnotations = function(shape) {
var modeling = this._modeling;
var sharedAnnotations = new Set(
getElementAnnotations(shape).map((entry) => entry.annotation)
);
if (!sharedAnnotations.size) {
return;
}
forEach(collectElementsAnnotations(shape.children), (entry) => {
if (sharedAnnotations.has(entry.annotation)) {
forEach(entry.associations, (association) => {
modeling.removeConnection(association);
});
}
});
};
/**
* Sets `hidden` property on all children of the given shape.
*
* @param {Element[]} elements
* @param {boolean} [hidden=false]
*
* @return {Element[]}
*/
SubProcessPlaneBehavior.prototype._showRecursively = function(elements, hidden) {
var self = this;
var result = [];
elements.forEach(function(element) {
element.hidden = !!hidden;
result = result.concat(element);
if (element.children) {
result = result.concat(
self._showRecursively(element.children, element.collapsed || hidden)
);
}
});
return result;
};
/**
* Adds a given root element to the BPMNDI diagrams.
*
* @param {Root|ModdleElement} planeElement
*
* @return {Root}
*/
SubProcessPlaneBehavior.prototype._addDiagram = function(planeElement) {
var bpmnjs = this._bpmnjs;
var diagrams = bpmnjs.getDefinitions().diagrams;
if (!planeElement.businessObject) {
planeElement = this._createNewDiagram(planeElement);
}
diagrams.push(planeElement.di.$parent);
return planeElement;
};
/**
* Creates a new plane element for the given sub process.
*
* @param {ModdleElement} bpmnElement
*
* @return {Root}
*/
SubProcessPlaneBehavior.prototype._createNewDiagram = function(bpmnElement) {
var bpmnFactory = this._bpmnFactory,
elementFactory = this._elementFactory;
var diPlane = bpmnFactory.create('bpmndi:BPMNPlane', {
bpmnElement: bpmnElement
});
var diDiagram = bpmnFactory.create('bpmndi:BPMNDiagram', {
plane: diPlane
});
diPlane.$parent = diDiagram;
// add a virtual element (not being drawn),
// a copy cat of our BpmnImporter code
var planeElement = elementFactory.createRoot({
id: getPlaneIdFromShape(bpmnElement),
type: bpmnElement.$type,
di: diPlane,
businessObject: bpmnElement,
collapsed: true
});
return planeElement;
};
/**
* Removes the diagram for a given root element.
*
* @param {Root} rootElement
*
* @return {ModdleElement}
*/
SubProcessPlaneBehavior.prototype._removeDiagram = function(rootElement) {
var bpmnjs = this._bpmnjs;
var diagrams = bpmnjs.getDefinitions().diagrams;
var removedDiagram = find(diagrams, function(diagram) {
return diagram.plane.bpmnElement.id === rootElement.id;
});
diagrams.splice(diagrams.indexOf(removedDiagram), 1);
return removedDiagram;
};
SubProcessPlaneBehavior.$inject = [
'canvas',
'eventBus',
'modeling',
'elementFactory',
'bpmnFactory',
'bpmnjs',
'elementRegistry'
];
/**
* Collect text annotations and their associations connected to the given elements.
*
* @param {Element[]} elements
*
* @return {Element[]}
*/
function collectAnnotationElements(elements) {
var result = [];
forEach(collectElementsAnnotations(elements), (entry) => {
result.push(entry.annotation);
result.push.apply(result, entry.associations);
});
return result;
}
/**
* Collect all children of a collapsed subprocess that need to be moved
* to or from its plane, including annotations and external labels that
* live at the process level by design(ref: https://github.com/camunda/camunda-modeler/issues/5163#issuecomment-3946571271)
*
* @param {Element} element
*
* @return {Element[]}
*/
function getSubProcessChildren(element) {
return element.children.slice()
.concat(collectAnnotationElements(element.children))
.concat(getExternalLabels(element));
}
/**
* Returns external labels of the given element's children and their descendants
* that are not direct children of the element (i.e. parented elsewhere).
*
* @param {Element} element
*
* @return {Element[]}
*/
function getExternalLabels(element) {
return selfAndChildren(element.children || [], true, -1)
.reduce(function(labels, child) {
if (child.label && child.label.parent !== element) {
labels.push(child.label);
}
return labels;
}, []);
}
================================================
FILE: lib/features/modeling/behavior/SubProcessStartEventBehavior.js
================================================
import inherits from 'inherits-browser';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import { is } from '../../../util/ModelUtil';
import { isExpanded } from '../../../util/DiUtil.js';
/**
* @typedef {import('didi').Injector} Injector
* @typedef {import('../Modeling').default} Modeling
*/
/**
* Add start event replacing element with expanded sub process.
*
* @param {Injector} injector
* @param {Modeling} modeling
*/
export default function SubProcessStartEventBehavior(injector, modeling) {
injector.invoke(CommandInterceptor, this);
this.postExecuted('shape.replace', function(event) {
var oldShape = event.context.oldShape,
newShape = event.context.newShape;
if (
!is(newShape, 'bpmn:SubProcess') ||
is(newShape,'bpmn:AdHocSubProcess') ||
! (is(oldShape, 'bpmn:Task') || is(oldShape, 'bpmn:CallActivity')) ||
!isExpanded(newShape)
) {
return;
}
var position = getStartEventPosition(newShape);
modeling.createShape({ type: 'bpmn:StartEvent' }, position, newShape);
});
}
SubProcessStartEventBehavior.$inject = [
'injector',
'modeling'
];
inherits(SubProcessStartEventBehavior, CommandInterceptor);
// helpers //////////
function getStartEventPosition(shape) {
return {
x: shape.x + shape.width / 6,
y: shape.y + shape.height / 2
};
}
================================================
FILE: lib/features/modeling/behavior/TextAnnotationBehavior.js
================================================
import inherits from 'inherits-browser';
import { is } from '../../../util/ModelUtil';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
/**
* @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
*/
export default function TextAnnotationBehavior(eventBus) {
CommandInterceptor.call(this, eventBus);
// On Append, TextAnnotations will be created on the Root.
// The default for connections will create the connection in the parent of
// the source element, so we overwrite the parent here.
this.preExecute('connection.create', function(context) {
const { target } = context;
if (!is(target, 'bpmn:TextAnnotation')) {
return;
}
context.parent = target.parent;
}, true);
this.preExecute([ 'shape.create', 'shape.resize', 'elements.move' ], function(context) {
const shapes = context.shapes || [ context.shape ];
if (shapes.length === 1 && is(shapes[0], 'bpmn:TextAnnotation')) {
context.hints = context.hints || {};
context.hints.autoResize = false;
}
}, true);
}
inherits(TextAnnotationBehavior, CommandInterceptor);
TextAnnotationBehavior.$inject = [
'eventBus'
];
================================================
FILE: lib/features/modeling/behavior/ToggleCollapseConnectionBehaviour.js
================================================
import inherits from 'inherits-browser';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import {
forEach
} from 'min-dash';
import { getMid } from 'diagram-js/lib/layout/LayoutUtil';
import { selfAndAllChildren } from 'diagram-js/lib/util/Elements';
import { isExpanded } from '../../../util/DiUtil';
import { is } from '../../../util/ModelUtil';
/**
* @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
* @typedef {import('../Modeling').default} Modeling
*
* @typedef {import('../../../model/Types').Element} Element
* @typedef {import('../../../model/Types').Shape} Shape
*
* @typedef {import('diagram-js/lib/util/Types').DirectionTRBL} DirectionTRBL
*/
/**
* @param {EventBus} eventBus
* @param {Modeling} modeling
*/
export default function ToggleCollapseConnectionBehaviour(
eventBus, modeling
) {
CommandInterceptor.call(this, eventBus);
this.postExecuted('shape.toggleCollapse', 1500, function(context) {
var shape = context.shape;
// only change connections when collapsing
if (isExpanded(shape)) {
return;
}
var allChildren = selfAndAllChildren(shape);
allChildren.forEach(function(child) {
// Ensure that the connection array is not modified during iteration
var incomingConnections = child.incoming.slice(),
outgoingConnections = child.outgoing.slice();
forEach(incomingConnections, function(c) {
handleConnection(c, true);
});
forEach(outgoingConnections, function(c) {
handleConnection(c, false);
});
});
function handleConnection(c, incoming) {
if (allChildren.indexOf(c.source) !== -1 && allChildren.indexOf(c.target) !== -1) {
return;
}
// don't reconnect TextAnnotation connections,
// since they should stay connected and moved with its element to the subprocess plane
if (is(c, 'bpmn:Association') && (is(c.source, 'bpmn:TextAnnotation') || is(c.target, 'bpmn:TextAnnotation'))) {
return;
}
if (incoming) {
modeling.reconnectEnd(c, shape, getMid(shape));
} else {
modeling.reconnectStart(c, shape, getMid(shape));
}
}
}, true);
}
inherits(ToggleCollapseConnectionBehaviour, CommandInterceptor);
ToggleCollapseConnectionBehaviour.$inject = [
'eventBus',
'modeling',
];
================================================
FILE: lib/features/modeling/behavior/ToggleElementCollapseBehaviour.js
================================================
import inherits from 'inherits-browser';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import {
getDi,
is
} from '../../../util/ModelUtil';
import {
computeChildrenBBox
} from 'diagram-js/lib/features/resize/ResizeUtil';
/**
* @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
* @typedef {import('../ElementFactory').default} ElementFactory
* @typedef {import('../Modeling').default} Modeling
*/
var LOW_PRIORITY = 500;
/**
* @param {EventBus} eventBus
* @param {ElementFactory} elementFactory
* @param {Modeling} modeling
*/
export default function ToggleElementCollapseBehaviour(
eventBus, elementFactory, modeling) {
CommandInterceptor.call(this, eventBus);
function hideEmptyLabels(children) {
if (children.length) {
children.forEach(function(child) {
if (child.type === 'label' && !child.businessObject.name) {
child.hidden = true;
}
});
}
}
function expandedBounds(shape, defaultSize) {
var children = shape.children,
newBounds = defaultSize,
visibleElements,
visibleBBox;
visibleElements = filterVisible(children).concat([ shape ]);
visibleBBox = computeChildrenBBox(visibleElements);
if (visibleBBox) {
// center to visibleBBox with max(defaultSize, childrenBounds)
newBounds.width = Math.max(visibleBBox.width, newBounds.width);
newBounds.height = Math.max(visibleBBox.height, newBounds.height);
newBounds.x = visibleBBox.x + (visibleBBox.width - newBounds.width) / 2;
newBounds.y = visibleBBox.y + (visibleBBox.height - newBounds.height) / 2;
} else {
// center to collapsed shape with defaultSize
newBounds.x = shape.x + (shape.width - newBounds.width) / 2;
newBounds.y = shape.y + (shape.height - newBounds.height) / 2;
}
return newBounds;
}
function collapsedBounds(shape, defaultSize) {
return {
x: shape.x + (shape.width - defaultSize.width) / 2,
y: shape.y + (shape.height - defaultSize.height) / 2,
width: defaultSize.width,
height: defaultSize.height
};
}
this.executed([ 'shape.toggleCollapse' ], LOW_PRIORITY, function(e) {
var context = e.context,
shape = context.shape;
if (!is(shape, 'bpmn:SubProcess')) {
return;
}
if (!shape.collapsed) {
// all children got made visible through djs, hide empty labels
hideEmptyLabels(shape.children);
// remove collapsed marker
getDi(shape).isExpanded = true;
} else {
// place collapsed marker
getDi(shape).isExpanded = false;
}
});
this.reverted([ 'shape.toggleCollapse' ], LOW_PRIORITY, function(e) {
var context = e.context;
var shape = context.shape;
// revert removing/placing collapsed marker
if (!shape.collapsed) {
getDi(shape).isExpanded = true;
} else {
getDi(shape).isExpanded = false;
}
});
this.postExecuted([ 'shape.toggleCollapse' ], LOW_PRIORITY, function(e) {
var shape = e.context.shape,
defaultSize = elementFactory.getDefaultSize(shape),
newBounds;
if (shape.collapsed) {
// resize to default size of collapsed shapes
newBounds = collapsedBounds(shape, defaultSize);
} else {
// resize to bounds of max(visible children, defaultSize)
newBounds = expandedBounds(shape, defaultSize);
}
modeling.resizeShape(shape, newBounds, null, {
autoResize: shape.collapsed ? false : 'nwse'
});
});
}
inherits(ToggleElementCollapseBehaviour, CommandInterceptor);
ToggleElementCollapseBehaviour.$inject = [
'eventBus',
'elementFactory',
'modeling'
];
// helpers //////////////////////
function filterVisible(elements) {
return elements.filter(function(e) {
return !e.hidden;
});
}
================================================
FILE: lib/features/modeling/behavior/UnclaimIdBehavior.js
================================================
import inherits from 'inherits-browser';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import { is } from '../../../util/ModelUtil';
import { isExpanded } from '../../../util/DiUtil';
import { isLabel } from '../../../util/LabelUtil';
/**
* @typedef {import('diagram-js/lib/core/Canvas').default} Canvas
* @typedef {import('didi').Injector} Injector
* @typedef {import('../Modeling').default} Modeling
*
* @typedef {import('../../../model/Types').Moddle} Moddle
*/
/**
* Unclaims model IDs on element deletion.
*
* @param {Canvas} canvas
* @param {Injector} injector
* @param {Moddle} moddle
* @param {Modeling} modeling
*/
export default function UnclaimIdBehavior(canvas, injector, moddle, modeling) {
injector.invoke(CommandInterceptor, this);
this.preExecute('shape.delete', function(event) {
var context = event.context,
shape = context.shape,
shapeBo = shape.businessObject;
if (isLabel(shape)) {
return;
}
if (is(shape, 'bpmn:Participant') && isExpanded(shape)) {
moddle.ids.unclaim(shapeBo.processRef.id);
}
modeling.unclaimId(shapeBo.id, shapeBo);
});
this.preExecute('connection.delete', function(event) {
var context = event.context,
connection = context.connection,
connectionBo = connection.businessObject;
modeling.unclaimId(connectionBo.id, connectionBo);
});
this.preExecute('canvas.updateRoot', function() {
var rootElement = canvas.getRootElement(),
rootElementBo = rootElement.businessObject;
if (is(rootElement, 'bpmn:Collaboration')) {
moddle.ids.unclaim(rootElementBo.id);
}
});
}
inherits(UnclaimIdBehavior, CommandInterceptor);
UnclaimIdBehavior.$inject = [ 'canvas', 'injector', 'moddle', 'modeling' ];
================================================
FILE: lib/features/modeling/behavior/UnsetDefaultFlowBehavior.js
================================================
import inherits from 'inherits-browser';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import {
getBusinessObject,
is
} from '../../../util/ModelUtil';
/**
* @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
* @typedef {import('../Modeling').default} Modeling
*/
/**
* A behavior that unsets the Default property of sequence flow source on
* element delete, if the removed element is the Gateway or Task's default flow.
*
* @param {EventBus} eventBus
* @param {Modeling} modeling
*/
export default function DeleteSequenceFlowBehavior(eventBus, modeling) {
CommandInterceptor.call(this, eventBus);
this.preExecute('connection.delete', function(event) {
var context = event.context,
connection = context.connection,
source = connection.source;
if (isDefaultFlow(connection, source)) {
modeling.updateProperties(source, {
'default': null
});
}
});
}
inherits(DeleteSequenceFlowBehavior, CommandInterceptor);
DeleteSequenceFlowBehavior.$inject = [
'eventBus',
'modeling'
];
// helpers //////////////////////
function isDefaultFlow(connection, source) {
if (!is(connection, 'bpmn:SequenceFlow')) {
return false;
}
var sourceBo = getBusinessObject(source),
sequenceFlow = getBusinessObject(connection);
return sourceBo.get('default') === sequenceFlow;
}
================================================
FILE: lib/features/modeling/behavior/UpdateFlowNodeRefsBehavior.js
================================================
import inherits from 'inherits-browser';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import {
is
} from '../../../util/ModelUtil';
/**
* @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
* @typedef {import('../Modeling').default} Modeling
*/
var LOW_PRIORITY = 500,
HIGH_PRIORITY = 5000;
/**
* BPMN specific delete lane behavior.
*
* @param {EventBus} eventBus
* @param {Modeling} modeling
*/
export default function UpdateFlowNodeRefsBehavior(eventBus, modeling) {
CommandInterceptor.call(this, eventBus);
/**
* Update Lane#flowNodeRefs and FlowNode#lanes with every flow node
* move/resize and lane move/resize. Groups elements to recompute containments
* as efficient as possible.
*/
// the update context
var context;
function initContext() {
context = context || new UpdateContext();
context.enter();
return context;
}
function getContext() {
if (!context) {
throw new Error('out of bounds release');
}
return context;
}
function releaseContext() {
if (!context) {
throw new Error('out of bounds release');
}
var triggerUpdate = context.leave();
if (triggerUpdate) {
modeling.updateLaneRefs(context.flowNodes, context.lanes);
context = null;
}
return triggerUpdate;
}
var laneRefUpdateEvents = [
'spaceTool',
'lane.add',
'lane.resize',
'lane.split',
'elements.create',
'elements.delete',
'elements.move',
'shape.create',
'shape.delete',
'shape.move',
'shape.resize'
];
// listen to a lot of stuff to group lane updates
this.preExecute(laneRefUpdateEvents, HIGH_PRIORITY, function(event) {
initContext();
});
this.postExecuted(laneRefUpdateEvents, LOW_PRIORITY, function(event) {
releaseContext();
});
// Mark flow nodes + lanes that need an update
this.preExecute([
'shape.create',
'shape.move',
'shape.delete',
'shape.resize'
], function(event) {
var context = event.context,
shape = context.shape;
var updateContext = getContext();
// no need to update labels
if (shape.labelTarget) {
return;
}
if (is(shape, 'bpmn:Lane')) {
updateContext.addLane(shape);
}
if (is(shape, 'bpmn:FlowNode')) {
updateContext.addFlowNode(shape);
}
});
}
UpdateFlowNodeRefsBehavior.$inject = [
'eventBus',
'modeling'
];
inherits(UpdateFlowNodeRefsBehavior, CommandInterceptor);
function UpdateContext() {
this.flowNodes = [];
this.lanes = [];
this.counter = 0;
this.addLane = function(lane) {
this.lanes.push(lane);
};
this.addFlowNode = function(flowNode) {
this.flowNodes.push(flowNode);
};
this.enter = function() {
this.counter++;
};
this.leave = function() {
this.counter--;
return !this.counter;
};
}
================================================
FILE: lib/features/modeling/behavior/index.js
================================================
import AdaptiveLabelPositioningBehavior from './AdaptiveLabelPositioningBehavior';
import AppendBehavior from './AppendBehavior';
import AssociationBehavior from './AssociationBehavior';
import AttachEventBehavior from './AttachEventBehavior';
import BoundaryEventBehavior from './BoundaryEventBehavior';
import CompensateBoundaryEventBehavior from './CompensateBoundaryEventBehavior';
import CreateBehavior from './CreateBehavior';
import CreateDataObjectBehavior from './CreateDataObjectBehavior';
import CreateParticipantBehavior from './CreateParticipantBehavior';
import DataInputAssociationBehavior from './DataInputAssociationBehavior';
import DataStoreBehavior from './DataStoreBehavior';
import DeleteLaneBehavior from './DeleteLaneBehavior';
import DetachEventBehavior from './DetachEventBehavior';
import DropOnFlowBehavior from './DropOnFlowBehavior';
import EventBasedGatewayBehavior from './EventBasedGatewayBehavior';
import FixHoverBehavior from './FixHoverBehavior';
import GroupBehavior from './GroupBehavior';
import ImportDockingFix from './ImportDockingFix';
import IsHorizontalFix from './IsHorizontalFix';
import LabelBehavior from './LabelBehavior';
import LayoutConnectionBehavior from './LayoutConnectionBehavior';
import MessageFlowBehavior from './MessageFlowBehavior';
import NonInterruptingBehavior from './NonInterruptingBehavior';
import RemoveEmbeddedLabelBoundsBehavior from './RemoveEmbeddedLabelBoundsBehavior';
import RemoveElementBehavior from './RemoveElementBehavior';
import RemoveParticipantBehavior from './RemoveParticipantBehavior';
import ReplaceConnectionBehavior from './ReplaceConnectionBehavior';
import ReplaceElementBehaviour from './ReplaceElementBehaviour';
import ResizeBehavior from './ResizeBehavior';
import ResizeLaneBehavior from './ResizeLaneBehavior';
import RootElementReferenceBehavior from './RootElementReferenceBehavior';
import SpaceToolBehavior from './SpaceToolBehavior';
import SubProcessPlaneBehavior from './SubProcessPlaneBehavior';
import SubProcessStartEventBehavior from './SubProcessStartEventBehavior';
import TextAnnotationBehavior from './TextAnnotationBehavior';
import ToggleCollapseConnectionBehaviour from './ToggleCollapseConnectionBehaviour';
import ToggleElementCollapseBehaviour from './ToggleElementCollapseBehaviour';
import UnclaimIdBehavior from './UnclaimIdBehavior';
import UnsetDefaultFlowBehavior from './UnsetDefaultFlowBehavior';
import UpdateFlowNodeRefsBehavior from './UpdateFlowNodeRefsBehavior';
import SetCompensationActivityAfterPasteBehavior from './SetCompensationActivityAfterPasteBehavior';
/**
* @type { import('didi').ModuleDeclaration }
*/
export default {
__init__: [
'adaptiveLabelPositioningBehavior',
'appendBehavior',
'associationBehavior',
'attachEventBehavior',
'boundaryEventBehavior',
'compensateBoundaryEventBehaviour',
'createBehavior',
'createDataObjectBehavior',
'createParticipantBehavior',
'dataInputAssociationBehavior',
'dataStoreBehavior',
'deleteLaneBehavior',
'detachEventBehavior',
'dropOnFlowBehavior',
'eventBasedGatewayBehavior',
'fixHoverBehavior',
'groupBehavior',
'importDockingFix',
'isHorizontalFix',
'labelBehavior',
'layoutConnectionBehavior',
'messageFlowBehavior',
'nonInterruptingBehavior',
'removeElementBehavior',
'removeEmbeddedLabelBoundsBehavior',
'removeParticipantBehavior',
'replaceConnectionBehavior',
'replaceElementBehaviour',
'resizeBehavior',
'resizeLaneBehavior',
'rootElementReferenceBehavior',
'spaceToolBehavior',
'subProcessPlaneBehavior',
'subProcessStartEventBehavior',
'textAnnotationBehavior',
'toggleCollapseConnectionBehaviour',
'toggleElementCollapseBehaviour',
'unclaimIdBehavior',
'updateFlowNodeRefsBehavior',
'unsetDefaultFlowBehavior',
'setCompensationActivityAfterPasteBehavior'
],
adaptiveLabelPositioningBehavior: [ 'type', AdaptiveLabelPositioningBehavior ],
appendBehavior: [ 'type', AppendBehavior ],
associationBehavior: [ 'type', AssociationBehavior ],
attachEventBehavior: [ 'type', AttachEventBehavior ],
boundaryEventBehavior: [ 'type', BoundaryEventBehavior ],
compensateBoundaryEventBehaviour: [ 'type', CompensateBoundaryEventBehavior ],
createBehavior: [ 'type', CreateBehavior ],
createDataObjectBehavior: [ 'type', CreateDataObjectBehavior ],
createParticipantBehavior: [ 'type', CreateParticipantBehavior ],
dataInputAssociationBehavior: [ 'type', DataInputAssociationBehavior ],
dataStoreBehavior: [ 'type', DataStoreBehavior ],
deleteLaneBehavior: [ 'type', DeleteLaneBehavior ],
detachEventBehavior: [ 'type', DetachEventBehavior ],
dropOnFlowBehavior: [ 'type', DropOnFlowBehavior ],
eventBasedGatewayBehavior: [ 'type', EventBasedGatewayBehavior ],
fixHoverBehavior: [ 'type', FixHoverBehavior ],
groupBehavior: [ 'type', GroupBehavior ],
importDockingFix: [ 'type', ImportDockingFix ],
isHorizontalFix: [ 'type', IsHorizontalFix ],
labelBehavior: [ 'type', LabelBehavior ],
layoutConnectionBehavior: [ 'type', LayoutConnectionBehavior ],
messageFlowBehavior: [ 'type', MessageFlowBehavior ],
nonInterruptingBehavior: [ 'type', NonInterruptingBehavior ],
removeElementBehavior: [ 'type', RemoveElementBehavior ],
removeEmbeddedLabelBoundsBehavior: [ 'type', RemoveEmbeddedLabelBoundsBehavior ],
removeParticipantBehavior: [ 'type', RemoveParticipantBehavior ],
replaceConnectionBehavior: [ 'type', ReplaceConnectionBehavior ],
replaceElementBehaviour: [ 'type', ReplaceElementBehaviour ],
resizeBehavior: [ 'type', ResizeBehavior ],
resizeLaneBehavior: [ 'type', ResizeLaneBehavior ],
rootElementReferenceBehavior: [ 'type', RootElementReferenceBehavior ],
spaceToolBehavior: [ 'type', SpaceToolBehavior ],
subProcessPlaneBehavior: [ 'type', SubProcessPlaneBehavior ],
subProcessStartEventBehavior: [ 'type', SubProcessStartEventBehavior ],
textAnnotationBehavior: [ 'type', TextAnnotationBehavior ],
toggleCollapseConnectionBehaviour: [ 'type', ToggleCollapseConnectionBehaviour ],
toggleElementCollapseBehaviour : [ 'type', ToggleElementCollapseBehaviour ],
unclaimIdBehavior: [ 'type', UnclaimIdBehavior ],
unsetDefaultFlowBehavior: [ 'type', UnsetDefaultFlowBehavior ],
updateFlowNodeRefsBehavior: [ 'type', UpdateFlowNodeRefsBehavior ],
setCompensationActivityAfterPasteBehavior: [ 'type', SetCompensationActivityAfterPasteBehavior ]
};
================================================
FILE: lib/features/modeling/behavior/util/CategoryUtil.js
================================================
import {
add as collectionAdd,
remove as collectionRemove
} from 'diagram-js/lib/util/Collections';
/**
* @typedef {import('../../BpmnFactory').default} BpmnFactory
*
* @typedef {import('../../../model/Types').ModdleElement} ModdleElement
*/
/**
* Creates a new bpmn:CategoryValue inside a new bpmn:Category
*
* @param {BpmnFactory} bpmnFactory
*
* @return {ModdleElement}
*/
export function createCategory(bpmnFactory) {
return bpmnFactory.create('bpmn:Category');
}
/**
* Creates a new bpmn:CategoryValue inside a new bpmn:Category
*
* @param {BpmnFactory} bpmnFactory
*
* @return {ModdleElement}
*/
export function createCategoryValue(bpmnFactory) {
return bpmnFactory.create('bpmn:CategoryValue');
}
/**
* Adds category value to definitions
*
* @param {ModdleElement} categoryValue
* @param {ModdleElement} category
* @param {ModdleElement} definitions
*
* @return {ModdleElement}
*/
export function linkCategoryValue(categoryValue, category, definitions) {
collectionAdd(category.get('categoryValue'), categoryValue);
categoryValue.$parent = category;
collectionAdd(definitions.get('rootElements'), category);
category.$parent = definitions;
return categoryValue;
}
/**
* Unlink category value from parent
*
* @param {ModdleElement} categoryValue
*
* @return {ModdleElement}
*/
export function unlinkCategoryValue(categoryValue) {
var category = categoryValue.$parent;
if (category) {
collectionRemove(category.get('categoryValue'), categoryValue);
categoryValue.$parent = null;
}
return categoryValue;
}
/**
* Unlink category from parent
*
* @param {ModdleElement} category
*
* @return {ModdleElement}
*/
export function unlinkCategory(category) {
var definitions = category.$parent;
if (definitions) {
collectionRemove(definitions.get('rootElements'), category);
category.$parent = null;
}
return category;
}
================================================
FILE: lib/features/modeling/behavior/util/ConnectionLayoutUtil.js
================================================
import { getAnchorPointAdjustment } from './LayoutUtil';
/**
* @typedef {import('diagram-js/lib/util/Types').Point} Point
*
* @typedef {import('./LayoutUtil').FindNewLineStartIndexHints} FindNewLineStartIndexHints
*/
/**
* Calculate the new point after the connection waypoints got updated.
*
* @param {Point} position
* @param {Point[]} newWaypoints
* @param {Point[]} oldWaypoints
* @param {FindNewLineStartIndexHints} hints
*
* @return {Point}
*/
export function getConnectionAdjustment(position, newWaypoints, oldWaypoints, hints) {
return getAnchorPointAdjustment(position, newWaypoints, oldWaypoints, hints).point;
}
================================================
FILE: lib/features/modeling/behavior/util/GeometricUtil.js
================================================
export * from 'diagram-js/lib/features/bendpoints/GeometricUtil';
================================================
FILE: lib/features/modeling/behavior/util/LabelLayoutUtil.js
================================================
import { findNewLineStartIndex, getAnchorPointAdjustment } from './LayoutUtil';
import { getMid } from 'diagram-js/lib/layout/LayoutUtil';
/**
* @typedef {import('./LineAttachmentUtil').Attachment} Attachment
*
* @typedef {import('./LayoutUtil').FindNewLineStartIndexHints} FindNewLineStartIndexHints
*
* @typedef {import('../../../../model/Types').Label} Label
*
* @typedef {import('diagram-js/lib/util/Types').Point} Point
*/
/**
* @param {Point[]} oldWaypoints
* @param {Point[]} newWaypoints
* @param {Attachment} attachment
* @param {FindNewLineStartIndexHints} hints
*
* @return {number}
*/
export function findNewLabelLineStartIndex(oldWaypoints, newWaypoints, attachment, hints) {
return findNewLineStartIndex(oldWaypoints, newWaypoints, attachment, hints);
}
/**
* Calculate the required adjustment (move delta) for the given label
* after the connection waypoints got updated.
*
* @param {Label} label
* @param {Point[]} newWaypoints
* @param {Point[]} oldWaypoints
* @param {FindNewLineStartIndexHints} hints
*
* @return {Point}
*/
export function getLabelAdjustment(label, newWaypoints, oldWaypoints, hints) {
var labelPosition = getMid(label);
return getAnchorPointAdjustment(labelPosition, newWaypoints, oldWaypoints, hints).delta;
}
================================================
FILE: lib/features/modeling/behavior/util/LayoutUtil.js
================================================
import {
getDistancePointPoint,
rotateVector,
getAngle
} from './GeometricUtil';
import {
getAttachment
} from './LineAttachmentUtil';
import {
roundPoint
} from 'diagram-js/lib/layout/LayoutUtil';
/**
* @typedef {import('diagram-js/lib/util/Types').Point} Point
*
* @typedef {import('./LineAttachmentUtil').Attachment} Attachment
*
* @typedef { {
* point: Point;
* delta: Point;
* } } AnchorPointAdjustment
*
* @typedef { {
* segmentMove?: {
* segmentStartIndex: number;
* newSegmentStartIndex: number;
* };
* bendpointMove?: {
* insert: boolean;
* bendpointIndex: number;
* };
* connectionStart: boolean;
* connectionEnd: boolean;
* } } FindNewLineStartIndexHints
*/
/**
* @param {Point[]} oldWaypoints
* @param {Point[]} newWaypoints
* @param {Attachment} attachment
* @param {FindNewLineStartIndexHints} hints
*
* @return {number}
*/
export function findNewLineStartIndex(oldWaypoints, newWaypoints, attachment, hints) {
var index = attachment.segmentIndex;
var offset = newWaypoints.length - oldWaypoints.length;
// segmentMove happened
if (hints.segmentMove) {
var oldSegmentStartIndex = hints.segmentMove.segmentStartIndex,
newSegmentStartIndex = hints.segmentMove.newSegmentStartIndex;
// if point was on moved segment return new segment index
if (index === oldSegmentStartIndex) {
return newSegmentStartIndex;
}
// point is after new segment index
if (index >= newSegmentStartIndex) {
return (index + offset < newSegmentStartIndex) ? newSegmentStartIndex : index + offset;
}
// if point is before new segment index
return index;
}
// bendpointMove happened
if (hints.bendpointMove) {
var insert = hints.bendpointMove.insert,
bendpointIndex = hints.bendpointMove.bendpointIndex,
newIndex;
// waypoints length didnt change
if (offset === 0) {
return index;
}
// point behind new/removed bendpoint
if (index >= bendpointIndex) {
newIndex = insert ? index + 1 : index - 1;
}
// point before new/removed bendpoint
if (index < bendpointIndex) {
newIndex = index;
// decide point should take right or left segment
if (insert && attachment.type !== 'bendpoint' && bendpointIndex - 1 === index) {
var rel = relativePositionMidWaypoint(newWaypoints, bendpointIndex);
if (rel < attachment.relativeLocation) {
newIndex++;
}
}
}
return newIndex;
}
// start/end changed
if (offset === 0) {
return index;
}
if (hints.connectionStart && index === 0) {
return 0;
}
if (hints.connectionEnd && index === oldWaypoints.length - 2) {
return newWaypoints.length - 2;
}
// if nothing fits, take the middle segment
return Math.floor((newWaypoints.length - 2) / 2);
}
/**
* Calculate the required adjustment (move delta) for the given point
* after the connection waypoints got updated.
*
* @param {Point} position
* @param {Point[]} newWaypoints
* @param {Point[]} oldWaypoints
* @param {FindNewLineStartIndexHints} hints
*
* @return {AnchorPointAdjustment} result
*/
export function getAnchorPointAdjustment(position, newWaypoints, oldWaypoints, hints) {
var dx = 0,
dy = 0;
var oldPosition = {
point: position,
delta: { x: 0, y: 0 }
};
// get closest attachment
var attachment = getAttachment(position, oldWaypoints),
oldLabelLineIndex = attachment.segmentIndex,
newLabelLineIndex = findNewLineStartIndex(oldWaypoints, newWaypoints, attachment, hints);
// should never happen
// TODO(@janstuemmel): throw an error here when connectionSegmentMove is refactored
if (newLabelLineIndex < 0 ||
newLabelLineIndex > newWaypoints.length - 2 ||
newLabelLineIndex === null) {
return oldPosition;
}
var oldLabelLine = getLine(oldWaypoints, oldLabelLineIndex),
newLabelLine = getLine(newWaypoints, newLabelLineIndex),
oldFoot = attachment.position;
var relativeFootPosition = getRelativeFootPosition(oldLabelLine, oldFoot),
angleDelta = getAngleDelta(oldLabelLine, newLabelLine);
// special rule if label on bendpoint
if (attachment.type === 'bendpoint') {
var offset = newWaypoints.length - oldWaypoints.length,
oldBendpointIndex = attachment.bendpointIndex,
oldBendpoint = oldWaypoints[oldBendpointIndex];
// bendpoint position hasn't changed, return same position
if (newWaypoints.indexOf(oldBendpoint) !== -1) {
return oldPosition;
}
// new bendpoint and old bendpoint have same index, then just return the offset
if (offset === 0) {
var newBendpoint = newWaypoints[oldBendpointIndex];
dx = newBendpoint.x - attachment.position.x,
dy = newBendpoint.y - attachment.position.y;
return {
delta: {
x: dx,
y: dy
},
point: {
x: position.x + dx,
y: position.y + dy
}
};
}
// if bendpoints get removed
if (offset < 0 && oldBendpointIndex !== 0 && oldBendpointIndex < oldWaypoints.length - 1) {
relativeFootPosition = relativePositionMidWaypoint(oldWaypoints, oldBendpointIndex);
}
}
var newFoot = {
x: (newLabelLine[1].x - newLabelLine[0].x) * relativeFootPosition + newLabelLine[0].x,
y: (newLabelLine[1].y - newLabelLine[0].y) * relativeFootPosition + newLabelLine[0].y
};
// the rotated vector to label
var newLabelVector = rotateVector({
x: position.x - oldFoot.x,
y: position.y - oldFoot.y
}, angleDelta);
// the new relative position
dx = newFoot.x + newLabelVector.x - position.x;
dy = newFoot.y + newLabelVector.y - position.y;
return {
point: roundPoint(newFoot),
delta: roundPoint({
x: dx,
y: dy
})
};
}
// HELPERS //////////////////////
function relativePositionMidWaypoint(waypoints, idx) {
var distanceSegment1 = getDistancePointPoint(waypoints[idx - 1], waypoints[idx]),
distanceSegment2 = getDistancePointPoint(waypoints[idx], waypoints[idx + 1]);
var relativePosition = distanceSegment1 / (distanceSegment1 + distanceSegment2);
return relativePosition;
}
function getAngleDelta(l1, l2) {
var a1 = getAngle(l1),
a2 = getAngle(l2);
return a2 - a1;
}
function getLine(waypoints, idx) {
return [ waypoints[idx], waypoints[idx + 1] ];
}
function getRelativeFootPosition(line, foot) {
var length = getDistancePointPoint(line[0], line[1]),
lengthToFoot = getDistancePointPoint(line[0], foot);
return length === 0 ? 0 : lengthToFoot / length;
}
================================================
FILE: lib/features/modeling/behavior/util/LineAttachmentUtil.js
================================================
/**
* @typedef {import('diagram-js/lib/util/Types').Point} Point
*
* @typedef { {
* type: 'bendpoint' | 'segment';
* position: Point;
* segmentIndex: number;
* bendpointIndex?: number;
* relativeLocation?: number;
* } } Attachment
*/
var sqrt = Math.sqrt,
min = Math.min,
max = Math.max,
abs = Math.abs;
/**
* Calculate the square (power to two) of a number.
*
* @param {number} n
*
* @return {number}
*/
function sq(n) {
return Math.pow(n, 2);
}
/**
* Get distance between two points.
*
* @param {Point} p1
* @param {Point} p2
*
* @return {number}
*/
function getDistance(p1, p2) {
return sqrt(sq(p1.x - p2.x) + sq(p1.y - p2.y));
}
/**
* Return the attachment of the given point on the specified line.
*
* The attachment is either a bendpoint (attached to the given point)
* or segment (attached to a location on a line segment) attachment:
*
* ```javascript
* var pointAttachment = {
* type: 'bendpoint',
* bendpointIndex: 3,
* position: { x: 10, y: 10 } // the attach point on the line
* };
*
* var segmentAttachment = {
* type: 'segment',
* segmentIndex: 2,
* relativeLocation: 0.31, // attach point location between 0 (at start) and 1 (at end)
* position: { x: 10, y: 10 } // the attach point on the line
* };
* ```
*
* @param {Point} point
* @param {Point[]} line
*
* @return {Attachment}
*/
export function getAttachment(point, line) {
var idx = 0,
segmentStart,
segmentEnd,
segmentStartDistance,
segmentEndDistance,
attachmentPosition,
minDistance,
intersections,
attachment,
attachmentDistance,
closestAttachmentDistance,
closestAttachment;
for (idx = 0; idx < line.length - 1; idx++) {
segmentStart = line[idx];
segmentEnd = line[idx + 1];
if (pointsEqual(segmentStart, segmentEnd)) {
intersections = [ segmentStart ];
} else {
segmentStartDistance = getDistance(point, segmentStart);
segmentEndDistance = getDistance(point, segmentEnd);
minDistance = min(segmentStartDistance, segmentEndDistance);
intersections = getCircleSegmentIntersections(segmentStart, segmentEnd, point, minDistance);
}
if (intersections.length < 1) {
throw new Error('expected between [1, 2] circle -> line intersections');
}
// one intersection -> bendpoint attachment
if (intersections.length === 1) {
attachment = {
type: 'bendpoint',
position: intersections[0],
segmentIndex: idx,
bendpointIndex: pointsEqual(segmentStart, intersections[0]) ? idx : idx + 1
};
}
// two intersections -> segment attachment
if (intersections.length === 2) {
attachmentPosition = mid(intersections[0], intersections[1]);
attachment = {
type: 'segment',
position: attachmentPosition,
segmentIndex: idx,
relativeLocation: getDistance(segmentStart, attachmentPosition) / getDistance(segmentStart, segmentEnd)
};
}
attachmentDistance = getDistance(attachment.position, point);
if (!closestAttachment || closestAttachmentDistance > attachmentDistance) {
closestAttachment = attachment;
closestAttachmentDistance = attachmentDistance;
}
}
return closestAttachment;
}
/**
* Get the intersection between a circle and a line segment.
*
* @param {Point} s1 segment start
* @param {Point} s2 segment end
* @param {Point} cc circle center
* @param {number} cr circle radius
*
* @return {Point[]} intersections
*/
function getCircleSegmentIntersections(s1, s2, cc, cr) {
var baX = s2.x - s1.x;
var baY = s2.y - s1.y;
var caX = cc.x - s1.x;
var caY = cc.y - s1.y;
var a = baX * baX + baY * baY;
var bBy2 = baX * caX + baY * caY;
var c = caX * caX + caY * caY - cr * cr;
var pBy2 = bBy2 / a;
var q = c / a;
var disc = pBy2 * pBy2 - q;
// check against negative value to work around
// negative, very close to zero results (-4e-15)
// being produced in some environments
if (disc < 0 && disc > -0.000001) {
disc = 0;
}
if (disc < 0) {
return [];
}
// if disc == 0 ... dealt with later
var tmpSqrt = sqrt(disc);
var abScalingFactor1 = -pBy2 + tmpSqrt;
var abScalingFactor2 = -pBy2 - tmpSqrt;
var i1 = {
x: s1.x - baX * abScalingFactor1,
y: s1.y - baY * abScalingFactor1
};
if (disc === 0) { // abScalingFactor1 == abScalingFactor2
return [ i1 ];
}
var i2 = {
x: s1.x - baX * abScalingFactor2,
y: s1.y - baY * abScalingFactor2
};
// return only points on line segment
return [ i1, i2 ].filter(function(p) {
return isPointInSegment(p, s1, s2);
});
}
function isPointInSegment(p, segmentStart, segmentEnd) {
return (
fenced(p.x, segmentStart.x, segmentEnd.x) &&
fenced(p.y, segmentStart.y, segmentEnd.y)
);
}
function fenced(n, rangeStart, rangeEnd) {
// use matching threshold to work around
// precision errors in intersection computation
return (
n >= min(rangeStart, rangeEnd) - EQUAL_THRESHOLD &&
n <= max(rangeStart, rangeEnd) + EQUAL_THRESHOLD
);
}
/**
* Calculate the mid between two points.
*
* @param {Point} p1
* @param {Point} p2
*
* @return {Point}
*/
function mid(p1, p2) {
return {
x: (p1.x + p2.x) / 2,
y: (p1.y + p2.y) / 2
};
}
var EQUAL_THRESHOLD = 0.1;
function pointsEqual(p1, p2) {
return (
abs(p1.x - p2.x) <= EQUAL_THRESHOLD &&
abs(p1.y - p2.y) <= EQUAL_THRESHOLD
);
}
================================================
FILE: lib/features/modeling/behavior/util/LineIntersect.js
================================================
/**
* @typedef {import('diagram-js/lib/util/Types').Point} Point
*/
/**
* Returns the intersection between two line segments a and b.
*
* @param {Point} l1s
* @param {Point} l1e
* @param {Point} l2s
* @param {Point} l2e
*
* @return {Point}
*/
export default function lineIntersect(l1s, l1e, l2s, l2e) {
// if the lines intersect, the result contains the x and y of the
// intersection (treating the lines as infinite) and booleans for
// whether line segment 1 or line segment 2 contain the point
var denominator, a, b, c, numerator;
denominator = ((l2e.y - l2s.y) * (l1e.x - l1s.x)) - ((l2e.x - l2s.x) * (l1e.y - l1s.y));
if (denominator == 0) {
return null;
}
a = l1s.y - l2s.y;
b = l1s.x - l2s.x;
numerator = ((l2e.x - l2s.x) * a) - ((l2e.y - l2s.y) * b);
c = numerator / denominator;
// if we cast these lines infinitely in
// both directions, they intersect here
return {
x: Math.round(l1s.x + (c * (l1e.x - l1s.x))),
y: Math.round(l1s.y + (c * (l1e.y - l1s.y)))
};
}
================================================
FILE: lib/features/modeling/behavior/util/NonInterruptingUtil.js
================================================
import { isEventSubProcess } from '../../../../util/DiUtil';
import { getBusinessObject, is } from '../../../../util/ModelUtil';
export const NON_INTERRUPTING_EVENT_TYPES = [
'bpmn:MessageEventDefinition',
'bpmn:TimerEventDefinition',
'bpmn:EscalationEventDefinition',
'bpmn:ConditionalEventDefinition',
'bpmn:SignalEventDefinition'
];
export function canBeNonInterrupting(shape) {
const businessObject = getBusinessObject(shape);
if (
!is(businessObject, 'bpmn:BoundaryEvent') &&
!(is(businessObject, 'bpmn:StartEvent') && isEventSubProcess(businessObject.$parent))
) {
return false;
}
const eventDefinitions = businessObject.get('eventDefinitions');
if (!eventDefinitions || !eventDefinitions.length) {
return false;
}
return NON_INTERRUPTING_EVENT_TYPES.some(event => is(eventDefinitions[0], event));
}
export function getInterruptingProperty(shape) {
return is(shape, 'bpmn:BoundaryEvent') ? 'cancelActivity' : 'isInterrupting';
}
================================================
FILE: lib/features/modeling/behavior/util/ResizeUtil.js
================================================
export { getParticipantResizeConstraints } from '../ResizeBehavior';
================================================
FILE: lib/features/modeling/cmd/AddLaneHandler.js
================================================
import {
filter
} from 'min-dash';
import {
eachElement
} from 'diagram-js/lib/util/Elements';
import {
getLanesRoot,
getChildLanes,
LANE_INDENTATION
} from '../util/LaneUtil';
import {
isHorizontal
} from '../../../util/DiUtil';
/**
* @typedef {import('diagram-js/lib/command/CommandHandler').default} CommandHandler
*
* @typedef {import('../Modeling').default} Modeling
* @typedef {import('../../space-tool/BpmnSpaceTool').default} SpaceTool
*/
/**
* A handler that allows us to add a new lane
* above or below an existing one.
*
* @implements {CommandHandler}
*
* @param {Modeling} modeling
* @param {SpaceTool} spaceTool
*/
export default function AddLaneHandler(modeling, spaceTool) {
this._modeling = modeling;
this._spaceTool = spaceTool;
}
AddLaneHandler.$inject = [
'modeling',
'spaceTool'
];
AddLaneHandler.prototype.preExecute = function(context) {
var spaceTool = this._spaceTool,
modeling = this._modeling;
var shape = context.shape,
location = context.location;
var lanesRoot = getLanesRoot(shape);
var isRoot = lanesRoot === shape,
laneParent = isRoot ? shape : shape.parent;
var existingChildLanes = getChildLanes(laneParent);
var isHorizontalLane = isHorizontal(shape);
// never mix up horizontal/vertical lanes
if (isHorizontalLane) {
if (location === 'left') {
location = 'top';
} else if (location === 'right') {
location = 'bottom';
}
} else {
if (location === 'top') {
location = 'left';
} else if (location === 'bottom') {
location = 'right';
}
}
// (0) add a lane if we currently got none and are adding to root
if (!existingChildLanes.length) {
var siblingPosition = isHorizontalLane ? {
x: shape.x + LANE_INDENTATION,
y: shape.y,
width: shape.width - LANE_INDENTATION,
height: shape.height
} : {
x: shape.x,
y: shape.y + LANE_INDENTATION,
width: shape.width,
height: shape.height - LANE_INDENTATION
};
modeling.createShape(
{
type: 'bpmn:Lane',
isHorizontal: isHorizontalLane
},
siblingPosition,
laneParent
);
}
// (1) collect affected elements to create necessary space
var allAffected = [];
eachElement(lanesRoot, function(element) {
allAffected.push(element);
// handle element labels in the diagram root
if (element.label) {
allAffected.push(element.label);
}
if (element === shape) {
return [];
}
return filter(element.children, function(c) {
return c !== shape;
});
});
var offset,
lanePosition,
spacePos,
direction,
axis;
if (location === 'top') {
offset = -120;
lanePosition = shape.y;
spacePos = lanePosition + 10;
direction = 'n';
axis = 'y';
} else if (location === 'left') {
offset = -120;
lanePosition = shape.x;
spacePos = lanePosition + 10;
direction = 'w';
axis = 'x';
} else if (location === 'bottom') {
offset = 120;
lanePosition = shape.y + shape.height;
spacePos = lanePosition - 10;
direction = 's';
axis = 'y';
} else if (location === 'right') {
offset = 120;
lanePosition = shape.x + shape.width;
spacePos = lanePosition - 10;
direction = 'e';
axis = 'x';
}
var adjustments = spaceTool.calculateAdjustments(allAffected, axis, offset, spacePos);
var delta = isHorizontalLane ? { x: 0, y: offset } : { x: offset, y: 0 };
spaceTool.makeSpace(
adjustments.movingShapes,
adjustments.resizingShapes,
delta,
direction,
spacePos
);
// (2) create new lane at open space
var newLanePosition = isHorizontalLane ? {
x: shape.x + (isRoot ? LANE_INDENTATION : 0),
y: lanePosition - (location === 'top' ? 120 : 0),
width: shape.width - (isRoot ? LANE_INDENTATION : 0),
height: 120
} : {
x: lanePosition - (location === 'left' ? 120 : 0),
y: shape.y + (isRoot ? LANE_INDENTATION : 0),
width: 120,
height: shape.height - (isRoot ? LANE_INDENTATION : 0)
};
context.newLane = modeling.createShape(
{
type: 'bpmn:Lane',
isHorizontal: isHorizontalLane
},
newLanePosition,
laneParent
);
};
================================================
FILE: lib/features/modeling/cmd/IdClaimHandler.js
================================================
/**
* @typedef {import('diagram-js/lib/command/CommandHandler').default} CommandHandler
*
* @typedef {import('../../../model/Types').Moddle} Moddle
*/
/**
* @implements {CommandHandler}
*
* @param {Moddle} moddle
*/
export default function IdClaimHandler(moddle) {
this._moddle = moddle;
}
IdClaimHandler.$inject = [ 'moddle' ];
IdClaimHandler.prototype.execute = function(context) {
var ids = this._moddle.ids,
id = context.id,
element = context.element,
claiming = context.claiming;
if (claiming) {
ids.claim(id, element);
} else {
ids.unclaim(id);
}
return [];
};
/**
* Command revert implementation.
*/
IdClaimHandler.prototype.revert = function(context) {
var ids = this._moddle.ids,
id = context.id,
element = context.element,
claiming = context.claiming;
if (claiming) {
ids.unclaim(id);
} else {
ids.claim(id, element);
}
return [];
};
================================================
FILE: lib/features/modeling/cmd/ResizeLaneHandler.js
================================================
import { is } from '../../../util/ModelUtil';
import {
getLanesRoot,
computeLanesResize
} from '../util/LaneUtil';
import {
eachElement
} from 'diagram-js/lib/util/Elements';
import {
asTRBL
} from 'diagram-js/lib/layout/LayoutUtil';
import {
substractTRBL
} from 'diagram-js/lib/features/resize/ResizeUtil';
/**
* @typedef {import('diagram-js/lib/command/CommandHandler').default} CommandHandler
*
* @typedef {import('../Modeling').default} Modeling
* @typedef {import('../../space-tool/BpmnSpaceTool').default} SpaceTool
*
* @typedef {import('../../../model/Types').Shape} Shape
*
* @typedef {import('diagram-js/lib/util/Types').Rect} Rect
*/
/**
* A handler that resizes a lane.
*
* @implements {CommandHandler}
*
* @param {Modeling} modeling
* @param {SpaceTool} spaceTool
*/
export default function ResizeLaneHandler(modeling, spaceTool) {
this._modeling = modeling;
this._spaceTool = spaceTool;
}
ResizeLaneHandler.$inject = [
'modeling',
'spaceTool'
];
ResizeLaneHandler.prototype.preExecute = function(context) {
var shape = context.shape,
newBounds = context.newBounds,
balanced = context.balanced;
if (balanced !== false) {
this.resizeBalanced(shape, newBounds);
} else {
this.resizeSpace(shape, newBounds);
}
};
/**
* Resize balanced, adjusting next / previous lane sizes.
*
* @param {Shape} shape
* @param {Rect} newBounds
*/
ResizeLaneHandler.prototype.resizeBalanced = function(shape, newBounds) {
var modeling = this._modeling;
var resizeNeeded = computeLanesResize(shape, newBounds);
// resize the lane
modeling.resizeShape(shape, newBounds);
// resize other lanes as needed
resizeNeeded.forEach(function(r) {
modeling.resizeShape(r.shape, r.newBounds);
});
};
/**
* Resize, making actual space and moving below / above elements.
*
* @param {Shape} shape
* @param {Rect} newBounds
*/
ResizeLaneHandler.prototype.resizeSpace = function(shape, newBounds) {
var spaceTool = this._spaceTool;
var shapeTrbl = asTRBL(shape),
newTrbl = asTRBL(newBounds);
var trblDiff = substractTRBL(newTrbl, shapeTrbl);
var lanesRoot = getLanesRoot(shape);
var allAffected = [],
allLanes = [];
eachElement(lanesRoot, function(element) {
allAffected.push(element);
if (is(element, 'bpmn:Lane') || is(element, 'bpmn:Participant')) {
allLanes.push(element);
}
return element.children;
});
var change,
spacePos,
direction,
offset,
adjustments;
if (trblDiff.bottom || trblDiff.top) {
change = trblDiff.bottom || trblDiff.top;
spacePos = shape.y + (trblDiff.bottom ? shape.height : 0) + (trblDiff.bottom ? -10 : 10);
direction = trblDiff.bottom ? 's' : 'n';
offset = trblDiff.top > 0 || trblDiff.bottom < 0 ? -change : change;
adjustments = spaceTool.calculateAdjustments(allAffected, 'y', offset, spacePos);
spaceTool.makeSpace(adjustments.movingShapes, adjustments.resizingShapes, { x: 0, y: change }, direction);
}
if (trblDiff.left || trblDiff.right) {
change = trblDiff.right || trblDiff.left;
spacePos = shape.x + (trblDiff.right ? shape.width : 0) + (trblDiff.right ? -10 : 100);
direction = trblDiff.right ? 'e' : 'w';
offset = trblDiff.left > 0 || trblDiff.right < 0 ? -change : change;
adjustments = spaceTool.calculateAdjustments(allLanes, 'x', offset, spacePos);
spaceTool.makeSpace(adjustments.movingShapes, adjustments.resizingShapes, { x: change, y: 0 }, direction);
}
};
================================================
FILE: lib/features/modeling/cmd/SetColorHandler.js
================================================
import {
assign,
forEach,
isString,
pick
} from 'min-dash';
import {
getDi,
isAny
} from '../../../util/ModelUtil';
import {
isLabel
} from '../../../util/LabelUtil';
import { isConnection } from 'diagram-js/lib/util/ModelUtil';
/**
* @typedef {import('diagram-js/lib/command/CommandHandler').default} CommandHandler
*
* @typedef {import('diagram-js/lib/command/CommandStack').default} CommandStack
*
* @typedef {import('../../../model/Types').ModdleElement} ModdleElement
*/
var DEFAULT_COLORS = {
fill: undefined,
stroke: undefined
};
/**
* @implements {CommandHandler}
*
* @param {CommandStack} commandStack
*/
export default function SetColorHandler(commandStack) {
this._commandStack = commandStack;
this._normalizeColor = function(color) {
// Remove color for falsy values.
if (!color) {
return undefined;
}
if (isString(color)) {
var hexColor = colorToHex(color);
if (hexColor) {
return hexColor;
}
}
throw new Error(`invalid color value: ${ color }`);
};
}
SetColorHandler.$inject = [
'commandStack'
];
SetColorHandler.prototype.postExecute = function(context) {
var elements = context.elements,
colors = context.colors || DEFAULT_COLORS;
var self = this;
var di = {};
if ('fill' in colors) {
assign(di, {
'background-color': this._normalizeColor(colors.fill) });
}
if ('stroke' in colors) {
assign(di, {
'border-color': this._normalizeColor(colors.stroke) });
}
forEach(elements, function(element) {
var assignedDi = isConnection(element) ? pick(di, [ 'border-color' ]) : di,
elementDi = getDi(element);
// TODO @barmac: remove once we drop bpmn.io properties
ensureLegacySupport(assignedDi);
if (isLabel(element)) {
// set label colors as bpmndi:BPMNLabel#color
self._commandStack.execute('element.updateModdleProperties', {
element: element,
moddleElement: elementDi.label,
properties: {
color: di['border-color']
}
});
} else {
// ignore non-compliant di
if (!isAny(elementDi, [ 'bpmndi:BPMNEdge', 'bpmndi:BPMNShape' ])) {
return;
}
// set colors bpmndi:BPMNEdge or bpmndi:BPMNShape
self._commandStack.execute('element.updateProperties', {
element: element,
properties: {
di: assignedDi
}
});
}
});
};
/**
* Convert color from rgb(a)/hsl to hex. Returns `null` for unknown color names
* and for colors with alpha less than 1.0. This depends on ``
* serialization of the `context.fillStyle`.
* Cf. https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-fillstyle
*
* @example
*
* ```javascript
* colorToHex('fuchsia'); // "#ff00ff"
*
* colorToHex('rgba(1, 2, 3, 0.4)'); // null
* ```
*
* @param {string} color
*
* @return {string|null}
*/
function colorToHex(color) {
var context = document.createElement('canvas').getContext('2d');
// (0) Start with transparent to account for browser default values.
context.fillStyle = 'transparent';
// (1) Assign color so that it's serialized.
context.fillStyle = color;
// (2) Return null for non-hex serialization result.
return /^#[0-9a-fA-F]{6}$/.test(context.fillStyle) ? context.fillStyle : null;
}
/**
* Add legacy properties if required.
*
* @param {ModdleElement} di
*/
function ensureLegacySupport(di) {
if ('border-color' in di) {
di.stroke = di['border-color'];
}
if ('background-color' in di) {
di.fill = di['background-color'];
}
}
================================================
FILE: lib/features/modeling/cmd/SplitLaneHandler.js
================================================
import {
getChildLanes,
LANE_INDENTATION
} from '../util/LaneUtil';
import {
isHorizontal
} from '../../../util/DiUtil';
/**
* @typedef {import('diagram-js/lib/command/CommandHandler').default} CommandHandler
*
* @typedef {import('../Modeling').default} Modeling
*/
/**
* A handler that splits a lane into a number of sub-lanes,
* creating new sub lanes, if necessary.
*
* @implements {CommandHandler}
*
* @param {Modeling} modeling
*/
export default function SplitLaneHandler(modeling) {
this._modeling = modeling;
}
SplitLaneHandler.$inject = [
'modeling'
];
SplitLaneHandler.prototype.preExecute = function(context) {
var modeling = this._modeling;
var shape = context.shape,
newLanesCount = context.count;
var childLanes = getChildLanes(shape),
existingLanesCount = childLanes.length;
if (existingLanesCount > newLanesCount) {
throw new Error(`more than <${ newLanesCount }> child lanes`);
}
var isHorizontalLane = isHorizontal(shape);
var laneBaseSize = isHorizontalLane ? shape.height : shape.width;
var newLanesSize = Math.round(laneBaseSize / newLanesCount);
// Iterate from first to last in child lane order,
// resizing existing lanes and creating new ones
// so that they split the parent proportionally.
//
// Due to rounding related errors, the last lane
// needs to take up all the remaining space.
var laneSize,
laneBounds,
newLaneAttrs,
idx;
for (idx = 0; idx < newLanesCount; idx++) {
// if last lane
if (idx === newLanesCount - 1) {
laneSize = laneBaseSize - (newLanesSize * idx);
} else {
laneSize = newLanesSize;
}
laneBounds = isHorizontalLane ? {
x: shape.x + LANE_INDENTATION,
y: shape.y + idx * newLanesSize,
width: shape.width - LANE_INDENTATION,
height: laneSize
} : {
x: shape.x + idx * newLanesSize,
y: shape.y + LANE_INDENTATION,
width: laneSize,
height: shape.height - LANE_INDENTATION
};
if (idx < existingLanesCount) {
// resize existing lane
modeling.resizeShape(childLanes[idx], laneBounds);
} else {
// create a new lane at position
newLaneAttrs = {
type: 'bpmn:Lane',
isHorizontal: isHorizontalLane
};
modeling.createShape(newLaneAttrs, laneBounds, shape);
}
}
};
================================================
FILE: lib/features/modeling/cmd/UpdateCanvasRootHandler.js
================================================
import {
add as collectionAdd,
remove as collectionRemove
} from 'diagram-js/lib/util/Collections';
import { getDi } from '../../../util/ModelUtil';
/**
* @typedef {import('diagram-js/lib/command/CommandHandler').default} CommandHandler
*
* @typedef {import('diagram-js/lib/core/Canvas').default} Canvas
* @typedef {import('../Modeling').default} Modeling
*/
/**
* @implements {CommandHandler}
*
* @param {Canvas} canvas
* @param {Modeling} modeling
*/
export default function UpdateCanvasRootHandler(canvas, modeling) {
this._canvas = canvas;
this._modeling = modeling;
}
UpdateCanvasRootHandler.$inject = [
'canvas',
'modeling'
];
UpdateCanvasRootHandler.prototype.execute = function(context) {
var canvas = this._canvas;
var newRoot = context.newRoot,
newRootBusinessObject = newRoot.businessObject,
oldRoot = canvas.getRootElement(),
oldRootBusinessObject = oldRoot.businessObject,
bpmnDefinitions = oldRootBusinessObject.$parent,
diPlane = getDi(oldRoot);
// (1) replace process old <> new root
canvas.setRootElement(newRoot);
canvas.removeRootElement(oldRoot);
// (2) update root elements
collectionAdd(bpmnDefinitions.rootElements, newRootBusinessObject);
newRootBusinessObject.$parent = bpmnDefinitions;
collectionRemove(bpmnDefinitions.rootElements, oldRootBusinessObject);
oldRootBusinessObject.$parent = null;
// (3) wire di
oldRoot.di = null;
diPlane.bpmnElement = newRootBusinessObject;
newRoot.di = diPlane;
context.oldRoot = oldRoot;
// TODO(nikku): return changed elements?
// return [ newRoot, oldRoot ];
return [];
};
UpdateCanvasRootHandler.prototype.revert = function(context) {
var canvas = this._canvas;
var newRoot = context.newRoot,
newRootBusinessObject = newRoot.businessObject,
oldRoot = context.oldRoot,
oldRootBusinessObject = oldRoot.businessObject,
bpmnDefinitions = newRootBusinessObject.$parent,
diPlane = getDi(newRoot);
// (1) replace process old <> new root
canvas.setRootElement(oldRoot);
canvas.removeRootElement(newRoot);
// (2) update root elements
collectionRemove(bpmnDefinitions.rootElements, newRootBusinessObject);
newRootBusinessObject.$parent = null;
collectionAdd(bpmnDefinitions.rootElements, oldRootBusinessObject);
oldRootBusinessObject.$parent = bpmnDefinitions;
// (3) wire di
newRoot.di = null;
diPlane.bpmnElement = oldRootBusinessObject;
oldRoot.di = diPlane;
// TODO(nikku): return changed elements?
// return [ newRoot, oldRoot ];
return [];
};
================================================
FILE: lib/features/modeling/cmd/UpdateFlowNodeRefsHandler.js
================================================
import {
collectLanes,
getLanesRoot
} from '../util/LaneUtil';
import {
is
} from '../../../util/ModelUtil';
import {
add as collectionAdd,
remove as collectionRemove
} from 'diagram-js/lib/util/Collections';
import {
asTRBL
} from 'diagram-js/lib/layout/LayoutUtil';
/**
* @typedef {import('diagram-js/lib/command/CommandHandler').default} CommandHandler
*
* @typedef {import('diagram-js/lib/core/ElementRegistry').default} ElementRegistry
*
* @typedef {import('../../../model/Types').Shape} Shape
*/
var FLOW_NODE_REFS_ATTR = 'flowNodeRef',
LANES_ATTR = 'lanes';
/**
* A handler that updates lane refs on changed elements.
*
* @implements {CommandHandler}
*
* @param {ElementRegistry} elementRegistry
*/
export default function UpdateFlowNodeRefsHandler(elementRegistry) {
this._elementRegistry = elementRegistry;
}
UpdateFlowNodeRefsHandler.$inject = [
'elementRegistry'
];
/**
* @param {Shape} flowNodeShapes
* @param {Shape} laneShapes
*
* @return { {
* flowNode: Shape;
* add: Shape[];
* remove: Shape[];
* }[] }
*/
UpdateFlowNodeRefsHandler.prototype._computeUpdates = function(flowNodeShapes, laneShapes) {
var handledNodes = [];
var updates = [];
var participantCache = {};
var allFlowNodeShapes = [];
function isInLaneShape(element, laneShape) {
var laneTrbl = asTRBL(laneShape);
var elementMid = {
x: element.x + element.width / 2,
y: element.y + element.height / 2
};
return elementMid.x > laneTrbl.left &&
elementMid.x < laneTrbl.right &&
elementMid.y > laneTrbl.top &&
elementMid.y < laneTrbl.bottom;
}
function addFlowNodeShape(flowNodeShape) {
if (handledNodes.indexOf(flowNodeShape) === -1) {
allFlowNodeShapes.push(flowNodeShape);
handledNodes.push(flowNodeShape);
}
}
function getAllLaneShapes(flowNodeShape) {
var root = getLanesRoot(flowNodeShape);
if (!participantCache[root.id]) {
participantCache[root.id] = collectLanes(root);
}
return participantCache[root.id];
}
function getNewLanes(flowNodeShape) {
if (!flowNodeShape.parent) {
return [];
}
var allLaneShapes = getAllLaneShapes(flowNodeShape);
return allLaneShapes.filter(function(l) {
return isInLaneShape(flowNodeShape, l);
}).map(function(shape) {
return shape.businessObject;
});
}
laneShapes.forEach(function(laneShape) {
var root = getLanesRoot(laneShape);
if (!root || handledNodes.indexOf(root) !== -1) {
return;
}
var children = root.children.filter(function(c) {
return is(c, 'bpmn:FlowNode');
});
children.forEach(addFlowNodeShape);
handledNodes.push(root);
});
flowNodeShapes.forEach(addFlowNodeShape);
allFlowNodeShapes.forEach(function(flowNodeShape) {
var flowNode = flowNodeShape.businessObject;
var lanes = flowNode.get(LANES_ATTR),
remove = lanes.slice(),
add = getNewLanes(flowNodeShape);
updates.push({ flowNode: flowNode, remove: remove, add: add });
});
laneShapes.forEach(function(laneShape) {
var lane = laneShape.businessObject;
// lane got removed XX-)
if (!laneShape.parent) {
lane.get(FLOW_NODE_REFS_ATTR).forEach(function(flowNode) {
updates.push({ flowNode: flowNode, remove: [ lane ], add: [] });
});
}
});
return updates;
};
UpdateFlowNodeRefsHandler.prototype.execute = function(context) {
var updates = context.updates;
if (!updates) {
updates = context.updates = this._computeUpdates(context.flowNodeShapes, context.laneShapes);
}
updates.forEach(function(update) {
var flowNode = update.flowNode,
lanes = flowNode.get(LANES_ATTR);
// unwire old
update.remove.forEach(function(oldLane) {
collectionRemove(lanes, oldLane);
collectionRemove(oldLane.get(FLOW_NODE_REFS_ATTR), flowNode);
});
// wire new
update.add.forEach(function(newLane) {
collectionAdd(lanes, newLane);
collectionAdd(newLane.get(FLOW_NODE_REFS_ATTR), flowNode);
});
});
// TODO(nikku): return changed elements
// return [ ... ];
return [];
};
UpdateFlowNodeRefsHandler.prototype.revert = function(context) {
var updates = context.updates;
updates.forEach(function(update) {
var flowNode = update.flowNode,
lanes = flowNode.get(LANES_ATTR);
// unwire new
update.add.forEach(function(newLane) {
collectionRemove(lanes, newLane);
collectionRemove(newLane.get(FLOW_NODE_REFS_ATTR), flowNode);
});
// wire old
update.remove.forEach(function(oldLane) {
collectionAdd(lanes, oldLane);
collectionAdd(oldLane.get(FLOW_NODE_REFS_ATTR), flowNode);
});
});
// TODO(nikku): return changed elements
// return [ ... ];
return [];
};
================================================
FILE: lib/features/modeling/cmd/UpdateModdlePropertiesHandler.js
================================================
import {
reduce,
keys,
forEach
} from 'min-dash';
import {
is,
getBusinessObject
} from '../../../util/ModelUtil';
/**
* @typedef {import('diagram-js/lib/command/CommandHandler').default} CommandHandler
*
* @typedef {import('diagram-js/lib/core/ElementRegistry').default} ElementRegistry
*
* @typedef {import('../../../model/Types').Shape} Shape
* @typedef {import('../../../model/Types').ModdleElement} ModdleElement
*/
/**
* @implements {CommandHandler}
*
* @param {ElementRegistry} elementRegistry
*/
export default function UpdateModdlePropertiesHandler(elementRegistry) {
this._elementRegistry = elementRegistry;
}
UpdateModdlePropertiesHandler.$inject = [ 'elementRegistry' ];
UpdateModdlePropertiesHandler.prototype.execute = function(context) {
var element = context.element,
moddleElement = context.moddleElement,
properties = context.properties;
if (!moddleElement) {
throw new Error(' required');
}
// TODO(nikku): we need to ensure that ID properties
// are properly registered / unregistered via
// this._moddle.ids.assigned(id)
var changed = context.changed || this._getVisualReferences(moddleElement).concat(element);
var oldProperties = context.oldProperties || getModdleProperties(moddleElement, keys(properties));
setModdleProperties(moddleElement, properties);
context.oldProperties = oldProperties;
context.changed = changed;
return changed;
};
UpdateModdlePropertiesHandler.prototype.revert = function(context) {
var oldProperties = context.oldProperties,
moddleElement = context.moddleElement,
changed = context.changed;
setModdleProperties(moddleElement, oldProperties);
return changed;
};
/**
* Return visual references of given moddle element within the diagram.
*
* @param {ModdleElement} moddleElement
*
* @return {Shape[]}
*/
UpdateModdlePropertiesHandler.prototype._getVisualReferences = function(moddleElement) {
var elementRegistry = this._elementRegistry;
if (is(moddleElement, 'bpmn:DataObject')) {
return getAllDataObjectReferences(moddleElement, elementRegistry);
}
return [];
};
// helpers /////////////////
function getModdleProperties(moddleElement, propertyNames) {
return reduce(propertyNames, function(result, key) {
result[key] = moddleElement.get(key);
return result;
}, {});
}
function setModdleProperties(moddleElement, properties) {
forEach(properties, function(value, key) {
moddleElement.set(key, value);
});
}
function getAllDataObjectReferences(dataObject, elementRegistry) {
return elementRegistry.filter(function(element) {
return (
is(element, 'bpmn:DataObjectReference') &&
getBusinessObject(element).dataObjectRef === dataObject
);
});
}
================================================
FILE: lib/features/modeling/cmd/UpdatePropertiesHandler.js
================================================
import {
reduce,
keys,
forEach,
assign
} from 'min-dash';
import {
getBusinessObject,
getDi
} from '../../../util/ModelUtil';
/**
* @typedef {import('diagram-js/lib/command/CommandHandler').default} CommandHandler
* @typedef {import('diagram-js/lib/command/CommandStack').CommandContext} CommandContext
*
* @typedef {import('diagram-js/lib/core/ElementRegistry').default} ElementRegistry
* @typedef {import('../../../model/Types').Moddle} Moddle
* @typedef {import('../Modeling').default} Modeling
* @typedef {import('../../../draw/TextRenderer').default} TextRenderer
*
* @typedef {import('../../../model/Types').Element} Element
*/
var DEFAULT_FLOW = 'default',
ID = 'id',
DI = 'di';
var NULL_DIMENSIONS = {
width: 0,
height: 0
};
/**
* A handler that implements a BPMN 2.0 property update.
*
* This should be used to set simple properties on elements with
* an underlying BPMN business object.
*
* Use respective diagram-js provided handlers if you would
* like to perform automated modeling.
*
* @implements {CommandHandler}
*
* @param {ElementRegistry} elementRegistry
* @param {Moddle} moddle
* @param {Modeling} modeling
* @param {TextRenderer} textRenderer
*/
export default function UpdatePropertiesHandler(
elementRegistry, moddle,
modeling, textRenderer) {
this._elementRegistry = elementRegistry;
this._moddle = moddle;
this._modeling = modeling;
this._textRenderer = textRenderer;
}
UpdatePropertiesHandler.$inject = [
'elementRegistry',
'moddle',
'modeling',
'textRenderer'
];
// api //////////////////////
/**
* Update a BPMN element's properties.
*
* @param { {
* element: Element;
* properties: Record;
* } & CommandContext } context
*
* @return {Element[]}
*/
UpdatePropertiesHandler.prototype.execute = function(context) {
var element = context.element,
changed = [ element ];
if (!element) {
throw new Error('element required');
}
var elementRegistry = this._elementRegistry,
ids = this._moddle.ids;
var businessObject = element.businessObject,
properties = unwrapBusinessObjects(context.properties),
oldProperties = context.oldProperties || getProperties(element, properties);
if (isIdChange(properties, businessObject)) {
ids.unclaim(businessObject[ID]);
elementRegistry.updateId(element, properties[ID]);
ids.claim(properties[ID], businessObject);
}
// correctly indicate visual changes on default flow updates
if (DEFAULT_FLOW in properties) {
if (properties[DEFAULT_FLOW]) {
changed.push(elementRegistry.get(properties[DEFAULT_FLOW].id));
}
if (businessObject[DEFAULT_FLOW]) {
changed.push(elementRegistry.get(businessObject[DEFAULT_FLOW].id));
}
}
// update properties
setProperties(element, properties);
// store old values
context.oldProperties = oldProperties;
context.changed = changed;
// indicate changed on objects affected by the update
return changed;
};
UpdatePropertiesHandler.prototype.postExecute = function(context) {
var element = context.element,
label = element.label;
var text = label && getBusinessObject(label).name;
if (!text) {
return;
}
// get layouted text bounds and resize external
// external label accordingly
var newLabelBounds = this._textRenderer.getExternalLabelBounds(label, text);
this._modeling.resizeShape(label, newLabelBounds, NULL_DIMENSIONS);
};
/**
* Revert updating a BPMN element's properties.
*
* @param { {
* element: Element;
* properties: Record;
* oldProperties: Record;
* } & CommandContext } context
*
* @return {Element[]}
*/
UpdatePropertiesHandler.prototype.revert = function(context) {
var element = context.element,
properties = context.properties,
oldProperties = context.oldProperties,
businessObject = element.businessObject,
elementRegistry = this._elementRegistry,
ids = this._moddle.ids;
// update properties
setProperties(element, oldProperties);
if (isIdChange(properties, businessObject)) {
ids.unclaim(properties[ID]);
elementRegistry.updateId(element, oldProperties[ID]);
ids.claim(oldProperties[ID], businessObject);
}
return context.changed;
};
function isIdChange(properties, businessObject) {
return ID in properties && properties[ID] !== businessObject[ID];
}
function getProperties(element, properties) {
var propertyNames = keys(properties),
businessObject = element.businessObject,
di = getDi(element);
return reduce(propertyNames, function(result, key) {
// handle DI separately
if (key !== DI) {
result[key] = businessObject.get(key);
} else {
result[key] = getDiProperties(di, keys(properties.di));
}
return result;
}, {});
}
function getDiProperties(di, propertyNames) {
return reduce(propertyNames, function(result, key) {
result[key] = di && di.get(key);
return result;
}, {});
}
function setProperties(element, properties) {
var businessObject = element.businessObject,
di = getDi(element);
forEach(properties, function(value, key) {
if (key !== DI) {
businessObject.set(key, value);
} else {
// only update, if di exists
if (di) {
setDiProperties(di, value);
}
}
});
}
function setDiProperties(di, properties) {
forEach(properties, function(value, key) {
di.set(key, value);
});
}
var referencePropertyNames = [ 'default' ];
/**
* Make sure we unwrap the actual business object behind diagram element that
* may have been passed as arguments.
*
* @param {Record} properties
*
* @return {Record} unwrappedProps
*/
function unwrapBusinessObjects(properties) {
var unwrappedProps = assign({}, properties);
referencePropertyNames.forEach(function(name) {
if (name in properties) {
unwrappedProps[name] = getBusinessObject(unwrappedProps[name]);
}
});
return unwrappedProps;
}
================================================
FILE: lib/features/modeling/cmd/UpdateSemanticParentHandler.js
================================================
/**
* @typedef {import('diagram-js/lib/command/CommandHandler').default} CommandHandler
*
* @typedef {import('../BpmnUpdater').default} BpmnUpdater
*/
/**
* @implements {CommandHandler}
*
* @param {BpmnUpdater} bpmnUpdater
*/
export default function UpdateSemanticParentHandler(bpmnUpdater) {
this._bpmnUpdater = bpmnUpdater;
}
UpdateSemanticParentHandler.$inject = [ 'bpmnUpdater' ];
UpdateSemanticParentHandler.prototype.execute = function(context) {
var dataStoreBo = context.dataStoreBo,
dataStoreDi = context.dataStoreDi,
newSemanticParent = context.newSemanticParent,
newDiParent = context.newDiParent;
context.oldSemanticParent = dataStoreBo.$parent;
context.oldDiParent = dataStoreDi.$parent;
// update semantic parent
this._bpmnUpdater.updateSemanticParent(dataStoreBo, newSemanticParent);
// update DI parent
this._bpmnUpdater.updateDiParent(dataStoreDi, newDiParent);
return [];
};
UpdateSemanticParentHandler.prototype.revert = function(context) {
var dataStoreBo = context.dataStoreBo,
dataStoreDi = context.dataStoreDi,
oldSemanticParent = context.oldSemanticParent,
oldDiParent = context.oldDiParent;
// update semantic parent
this._bpmnUpdater.updateSemanticParent(dataStoreBo, oldSemanticParent);
// update DI parent
this._bpmnUpdater.updateDiParent(dataStoreDi, oldDiParent);
return [];
};
================================================
FILE: lib/features/modeling/index.js
================================================
import BehaviorModule from './behavior';
import RulesModule from '../rules';
import DiOrderingModule from '../di-ordering';
import OrderingModule from '../ordering';
import ReplaceModule from '../replace';
import SpaceToolModule from '../space-tool';
import CommandModule from 'diagram-js/lib/command';
import LabelSupportModule from 'diagram-js/lib/features/label-support';
import AttachSupportModule from 'diagram-js/lib/features/attach-support';
import SelectionModule from 'diagram-js/lib/features/selection';
import ChangeSupportModule from 'diagram-js/lib/features/change-support';
import BpmnFactory from './BpmnFactory';
import BpmnUpdater from './BpmnUpdater';
import ElementFactory from './ElementFactory';
import Modeling from './Modeling';
import BpmnLayouter from './BpmnLayouter';
import CroppingConnectionDocking from 'diagram-js/lib/layout/CroppingConnectionDocking';
export default {
__init__: [
'modeling',
'bpmnUpdater'
],
__depends__: [
BehaviorModule,
RulesModule,
DiOrderingModule,
OrderingModule,
ReplaceModule,
CommandModule,
LabelSupportModule,
AttachSupportModule,
SelectionModule,
ChangeSupportModule,
SpaceToolModule
],
bpmnFactory: [ 'type', BpmnFactory ],
bpmnUpdater: [ 'type', BpmnUpdater ],
elementFactory: [ 'type', ElementFactory ],
modeling: [ 'type', Modeling ],
layouter: [ 'type', BpmnLayouter ],
connectionDocking: [ 'type', CroppingConnectionDocking ]
};
================================================
FILE: lib/features/modeling/util/LaneUtil.js
================================================
import { is } from '../../../util/ModelUtil';
import {
getParent
} from './ModelingUtil';
import {
isHorizontal
} from '../../../util/DiUtil';
import {
asTRBL
} from 'diagram-js/lib/layout/LayoutUtil';
import {
substractTRBL,
resizeTRBL
} from 'diagram-js/lib/features/resize/ResizeUtil';
/**
* @typedef {import('../../../model/Types').Shape} Shape
*
* @typedef {import('diagram-js/lib/util/Types').Rect} Rect
*/
var abs = Math.abs;
function getTRBLResize(oldBounds, newBounds) {
return substractTRBL(asTRBL(newBounds), asTRBL(oldBounds));
}
var LANE_PARENTS = [
'bpmn:Participant',
'bpmn:Process',
'bpmn:SubProcess'
];
export var LANE_INDENTATION = 30;
/**
* Return all lanes that are children of the given shape.
*
* @param {Shape} shape
* @param {Shape[]} [collectedShapes]
*
* @return {Shape[]}
*/
export function collectLanes(shape, collectedShapes) {
collectedShapes = collectedShapes || [];
shape.children.filter(function(s) {
if (is(s, 'bpmn:Lane')) {
collectLanes(s, collectedShapes);
collectedShapes.push(s);
}
});
return collectedShapes;
}
/**
* Return all lanes that are direct children of the given shape.
*
* @param {Shape} shape
*
* @return {Shape[]}
*/
export function getChildLanes(shape) {
return shape.children.filter(function(c) {
return is(c, 'bpmn:Lane');
});
}
/**
* Return the parent shape of the given lane.
*
* @param {Shape} shape
*
* @return {Shape}
*/
export function getLanesRoot(shape) {
return getParent(shape, LANE_PARENTS) || shape;
}
/**
* Compute the required resize operations for lanes
* adjacent to the given shape, assuming it will be
* resized to the given new bounds.
*
* @param {Shape} shape
* @param {Rect} newBounds
*
* @return { {
* shape: Shape;
* newBounds: Rect;
* }[] }
*/
export function computeLanesResize(shape, newBounds) {
var rootElement = getLanesRoot(shape);
var initialShapes = is(rootElement, 'bpmn:Process') ? [] : [ rootElement ];
var allLanes = collectLanes(rootElement, initialShapes),
shapeTrbl = asTRBL(shape),
shapeNewTrbl = asTRBL(newBounds),
trblResize = getTRBLResize(shape, newBounds),
resizeNeeded = [];
var isHorizontalLane = isHorizontal(shape);
allLanes.forEach(function(other) {
if (other === shape) {
return;
}
var topResize = isHorizontalLane ? 0 : trblResize.top,
rightResize = isHorizontalLane ? trblResize.right : 0,
bottomResize = isHorizontalLane ? 0 : trblResize.bottom,
leftResize = isHorizontalLane ? trblResize.left : 0;
var otherTrbl = asTRBL(other);
if (trblResize.top) {
if (abs(otherTrbl.bottom - shapeTrbl.top) < 10) {
bottomResize = shapeNewTrbl.top - otherTrbl.bottom;
}
if (abs(otherTrbl.top - shapeTrbl.top) < 5) {
topResize = shapeNewTrbl.top - otherTrbl.top;
}
}
if (trblResize.left) {
if (abs(otherTrbl.right - shapeTrbl.left) < 10) {
rightResize = shapeNewTrbl.left - otherTrbl.right;
}
if (abs(otherTrbl.left - shapeTrbl.left) < 5) {
leftResize = shapeNewTrbl.left - otherTrbl.left;
}
}
if (trblResize.bottom) {
if (abs(otherTrbl.top - shapeTrbl.bottom) < 10) {
topResize = shapeNewTrbl.bottom - otherTrbl.top;
}
if (abs(otherTrbl.bottom - shapeTrbl.bottom) < 5) {
bottomResize = shapeNewTrbl.bottom - otherTrbl.bottom;
}
}
if (trblResize.right) {
if (abs(otherTrbl.left - shapeTrbl.right) < 10) {
leftResize = shapeNewTrbl.right - otherTrbl.left;
}
if (abs(otherTrbl.right - shapeTrbl.right) < 5) {
rightResize = shapeNewTrbl.right - otherTrbl.right;
}
}
if (topResize || rightResize || bottomResize || leftResize) {
resizeNeeded.push({
shape: other,
newBounds: resizeTRBL(other, {
top: topResize,
right: rightResize,
bottom: bottomResize,
left: leftResize
})
});
}
});
return resizeNeeded;
}
================================================
FILE: lib/features/modeling/util/ModelingUtil.js
================================================
import { isString } from 'min-dash';
export { is, isAny } from '../../../util/ModelUtil';
import {
is,
isAny,
getBusinessObject
} from '../../../util/ModelUtil';
import { isHorizontal } from '../../../util/DiUtil';
/**
* @typedef {import('diagram-js/lib/core/ElementRegistry').default} ElementRegistry
* @typedef {import('../../../model/Types').Element} Element
*/
/**
* Return the parent of the element with any of the given types.
*
* @param {Element} element
* @param {string|string[]} anyType
*
* @return {Element|null}
*/
export function getParent(element, anyType) {
if (isString(anyType)) {
anyType = [ anyType ];
}
while ((element = element.parent)) {
if (isAny(element, anyType)) {
return element;
}
}
return null;
}
/**
* Determines if the local modeling direction is vertical or horizontal.
*
* @param {Element} element
* @param {ElementRegistry} [elementRegistry] - provide to consider parent diagram direction
*
* @return {boolean} false for vertical pools, lanes and their children. true otherwise
*/
export function isDirectionHorizontal(element, elementRegistry) {
var parent = getParent(element, 'bpmn:Process');
if (parent) {
return true;
}
var types = [ 'bpmn:Participant', 'bpmn:Lane' ];
parent = getParent(element, types);
if (parent) {
return isHorizontal(parent);
} else if (isAny(element, types)) {
return isHorizontal(element);
}
var process;
for (process = getBusinessObject(element); process; process = process.$parent) {
if (is(process, 'bpmn:Process')) {
break;
}
}
if (!elementRegistry) {
return true;
}
// The direction may be specified in another diagram. We ignore that there
// could be multiple diagrams with contradicting properties based on the
// assumption that such BPMN files are unusual.
var pool = elementRegistry.find(function(shape) {
var businessObject = getBusinessObject(shape);
return businessObject && businessObject.get('processRef') === process;
});
if (!pool) {
return true;
}
return isHorizontal(pool);
}
================================================
FILE: lib/features/modeling-feedback/ModelingFeedback.js
================================================
import { is } from '../../util/ModelUtil';
/**
* @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
* @typedef {import('diagram-js/lib/features/tooltips/Tooltips').default} Tooltips
* @typedef {import('diagram-js/lib/i18n/translate/translate').default} Translate
*/
var COLLAB_ERR_MSG = 'flow elements must be children of pools/participants';
var DATA_OBJECT_ERR_MSG = 'Data object must be placed within a pool/participant.';
/**
* @param {EventBus} eventBus
* @param {Tooltips} tooltips
* @param {Translate} translate
*/
export default function ModelingFeedback(eventBus, tooltips, translate) {
function showError(position, message, timeout) {
tooltips.add({
position: {
x: position.x + 5,
y: position.y + 5
},
type: 'error',
timeout: timeout || 2000,
html: '' + message + '
'
});
}
eventBus.on([ 'shape.move.rejected', 'create.rejected' ], function(event) {
var context = event.context,
shape = context.shape,
target = context.target;
if (is(target, 'bpmn:Collaboration')) {
if (is(shape, 'bpmn:FlowNode')) {
showError(event, translate(COLLAB_ERR_MSG));
} else if (is(shape, 'bpmn:DataObjectReference')) {
showError(event, translate(DATA_OBJECT_ERR_MSG));
}
}
});
}
ModelingFeedback.$inject = [
'eventBus',
'tooltips',
'translate'
];
================================================
FILE: lib/features/modeling-feedback/index.js
================================================
import TooltipsModule from 'diagram-js/lib/features/tooltips';
import ModelingFeedback from './ModelingFeedback';
export default {
__depends__: [
TooltipsModule
],
__init__: [
'modelingFeedback'
],
modelingFeedback: [ 'type', ModelingFeedback ]
};
================================================
FILE: lib/features/ordering/BpmnOrderingProvider.js
================================================
import inherits from 'inherits-browser';
import OrderingProvider from 'diagram-js/lib/features/ordering/OrderingProvider';
import {
is,
isAny
} from '../modeling/util/ModelingUtil';
import {
findIndex,
find
} from 'min-dash';
/**
* @typedef {import('diagram-js/lib/core/Canvas').default} Canvas
* @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
*/
/**
* A BPMN-specific ordering provider.
*
* @param {EventBus} eventBus
* @param {Canvas} canvas
*/
export default function BpmnOrderingProvider(eventBus, canvas) {
OrderingProvider.call(this, eventBus);
var orders = [
{ type: 'bpmn:SubProcess', order: { level: 6 } },
// handle SequenceFlow(s) like message flows and render them always on top
{
type: 'bpmn:SequenceFlow',
order: {
level: 9,
containers: [
'bpmn:Participant',
'bpmn:FlowElementsContainer'
]
}
},
// handle DataAssociation(s) like message flows and render them always on top
{
type: 'bpmn:DataAssociation',
order: {
level: 9,
containers: [
'bpmn:Collaboration',
'bpmn:FlowElementsContainer'
]
}
},
{
type: 'bpmn:TextAnnotation',
order: {
level: 9
}
},
{
type: 'bpmn:MessageFlow', order: {
level: 9,
containers: [ 'bpmn:Collaboration' ]
}
},
{
type: 'bpmn:Association',
order: {
level: 6,
containers: [
'bpmn:Participant',
'bpmn:FlowElementsContainer',
'bpmn:Collaboration'
]
}
},
{ type: 'bpmn:BoundaryEvent', order: { level: 8 } },
{
type: 'bpmn:Group',
order: {
level: 10,
containers: [
'bpmn:Collaboration',
'bpmn:FlowElementsContainer'
]
}
},
{ type: 'bpmn:FlowElement', order: { level: 5 } },
{ type: 'bpmn:Participant', order: { level: -2 } },
{ type: 'bpmn:Lane', order: { level: -1 } }
];
function computeOrder(element) {
if (element.labelTarget) {
return { level: 10 };
}
var entry = find(orders, function(o) {
return isAny(element, [ o.type ]);
});
return entry && entry.order || { level: 1 };
}
function getOrder(element) {
var order = element.order;
if (!order) {
element.order = order = computeOrder(element);
}
if (!order) {
throw new Error(`no order for <${ element.id }>`);
}
return order;
}
function findActualParent(element, newParent, containers) {
var actualParent = newParent;
while (actualParent) {
if (isAny(actualParent, containers)) {
break;
}
actualParent = actualParent.parent;
}
if (!actualParent) {
throw new Error(`no parent for <${ element.id }> in <${ newParent && newParent.id }>`);
}
return actualParent;
}
this.getOrdering = function(element, newParent) {
// render labels and text annotations always on top
if (element.labelTarget || is(element, 'bpmn:TextAnnotation')) {
return {
parent: canvas.findRoot(newParent) || canvas.getRootElement(),
index: -1
};
}
var elementOrder = getOrder(element);
if (elementOrder.containers) {
newParent = findActualParent(element, newParent, elementOrder.containers);
}
var currentIndex = newParent.children.indexOf(element);
var insertIndex = findIndex(newParent.children, function(child) {
// do not compare with labels, they are created
// in the wrong order (right after elements) during import and
// mess up the positioning.
if (!element.labelTarget && child.labelTarget) {
return false;
}
return elementOrder.level < getOrder(child).level;
});
// if the element is already in the child list at
// a smaller index, we need to adjust the insert index.
// this takes into account that the element is being removed
// before being re-inserted
if (insertIndex !== -1) {
if (currentIndex !== -1 && currentIndex < insertIndex) {
insertIndex -= 1;
}
}
return {
index: insertIndex,
parent: newParent
};
};
}
BpmnOrderingProvider.$inject = [ 'eventBus', 'canvas' ];
inherits(BpmnOrderingProvider, OrderingProvider);
================================================
FILE: lib/features/ordering/index.js
================================================
import BpmnOrderingProvider from './BpmnOrderingProvider';
export default {
__init__: [ 'bpmnOrderingProvider' ],
bpmnOrderingProvider: [ 'type', BpmnOrderingProvider ]
};
================================================
FILE: lib/features/outline/OutlineProvider.js
================================================
import { assign } from 'min-dash';
import {
attr as svgAttr,
create as svgCreate
} from 'tiny-svg';
import {
is,
isAny
} from '../../util/ModelUtil';
import { isLabel, isExternalLabel } from '../../util/LabelUtil';
import {
DATA_OBJECT_REFERENCE_OUTLINE_PATH,
DATA_STORE_REFERENCE_OUTLINE_PATH,
DATA_OBJECT_REFERENCE_STANDARD_SIZE,
DATA_STORE_REFERENCE_STANDARD_SIZE,
createPath
} from './OutlineUtil';
/**
* @typedef { import('diagram-js/lib/features/outline/OutlineProvider').default } BaseOutlineProvider
*
* @typedef { import('diagram-js/lib/features/outline/OutlineProvider').Outline } Outline
*
* @typedef { import('diagram-js/lib/draw/Styles').default } Styles
*
* @typedef { import('diagram-js/lib/model/Types').Element } Element
*/
const DEFAULT_OFFSET = 5;
/**
* BPMN-specific outline provider.
*
* @implements {BaseOutlineProvider}
*
* @param {Outline} outline
* @param {Styles} styles
*/
export default function OutlineProvider(outline, styles) {
this._styles = styles;
outline.registerProvider(this);
}
OutlineProvider.$inject = [
'outline',
'styles'
];
/**
* Returns outline for a given element.
*
* @param {Element} element
*
* @return {Outline}
*/
OutlineProvider.prototype.getOutline = function(element) {
const OUTLINE_STYLE = this._styles.cls('djs-outline', [ 'no-fill' ]);
var outline;
if (isExternalLabel(element)) {
outline = svgCreate('rect');
svgAttr(outline, assign({
x: -DEFAULT_OFFSET,
y: -DEFAULT_OFFSET,
rx: 4,
width: element.width + DEFAULT_OFFSET * 2,
height: element.height + DEFAULT_OFFSET * 2
}, OUTLINE_STYLE));
return outline;
}
if (isLabel(element)) {
return;
}
if (is(element, 'bpmn:Gateway')) {
outline = svgCreate('rect');
assign(outline.style, {
'transform-box': 'fill-box',
'transform': 'rotate(45deg)',
'transform-origin': 'center'
});
svgAttr(outline, assign({
x: 2,
y: 2,
rx: 4,
width: element.width - 4,
height: element.height - 4,
}, OUTLINE_STYLE));
} else if (isAny(element, [ 'bpmn:Task', 'bpmn:SubProcess', 'bpmn:Group', 'bpmn:CallActivity' ])) {
outline = svgCreate('rect');
svgAttr(outline, assign({
x: -DEFAULT_OFFSET,
y: -DEFAULT_OFFSET,
rx: 14,
width: element.width + DEFAULT_OFFSET * 2,
height: element.height + DEFAULT_OFFSET * 2
}, OUTLINE_STYLE));
} else if (is(element, 'bpmn:EndEvent')) {
outline = svgCreate('circle');
// Extra 1px offset needed due to increased stroke-width of end event
// which makes it bigger than other events.
svgAttr(outline, assign({
cx: element.width / 2,
cy: element.height / 2,
r: element.width / 2 + DEFAULT_OFFSET + 1
}, OUTLINE_STYLE));
} else if (is(element, 'bpmn:Event')) {
outline = svgCreate('circle');
svgAttr(outline, assign({
cx: element.width / 2,
cy: element.height / 2,
r: element.width / 2 + DEFAULT_OFFSET
}, OUTLINE_STYLE));
} else if (is(element, 'bpmn:DataObjectReference') && isStandardSize(element, 'bpmn:DataObjectReference')) {
outline = createPath(
DATA_OBJECT_REFERENCE_OUTLINE_PATH,
{ x: -6, y: -6 },
OUTLINE_STYLE
);
} else if (is(element, 'bpmn:DataStoreReference') && isStandardSize(element, 'bpmn:DataStoreReference')) {
outline = createPath(
DATA_STORE_REFERENCE_OUTLINE_PATH,
{ x: -6, y: -6 },
OUTLINE_STYLE
);
}
return outline;
};
/**
* Updates the outline for a given element.
* Returns true if the update for the given element was handled by this provider.
*
* @param {Element} element
* @param {Outline} outline
* @returns {boolean}
*/
OutlineProvider.prototype.updateOutline = function(element, outline) {
if (isLabel(element)) {
return;
}
if (isAny(element, [ 'bpmn:SubProcess', 'bpmn:Group' ])) {
svgAttr(outline, {
width: element.width + DEFAULT_OFFSET * 2,
height: element.height + DEFAULT_OFFSET * 2
});
return true;
} else if (isAny(element, [
'bpmn:Event',
'bpmn:Gateway',
'bpmn:DataStoreReference',
'bpmn:DataObjectReference'
])) {
return true;
}
return false;
};
// helpers //////////
function isStandardSize(element, type) {
var standardSize;
if (type === 'bpmn:DataObjectReference') {
standardSize = DATA_OBJECT_REFERENCE_STANDARD_SIZE;
} else if (type === 'bpmn:DataStoreReference') {
standardSize = DATA_STORE_REFERENCE_STANDARD_SIZE;
}
return element.width === standardSize.width
&& element.height === standardSize.height;
}
================================================
FILE: lib/features/outline/OutlineUtil.js
================================================
import {
create as svgCreate
} from 'tiny-svg';
export const DATA_OBJECT_REFERENCE_OUTLINE_PATH = 'M44.7648 11.3263L36.9892 2.64074C36.0451 1.58628 34.5651 0.988708 33.1904 0.988708H5.98667C3.22688 0.988708 0.989624 3.34892 0.989624 6.26039V55.0235C0.989624 57.9349 3.22688 60.2952 5.98667 60.2952H40.966C43.7257 60.2952 45.963 57.9349 45.963 55.0235V14.9459C45.963 13.5998 45.6407 12.3048 44.7648 11.3263Z';
export const DATA_STORE_REFERENCE_OUTLINE_PATH = 'M1.03845 48.1347C1.03845 49.3511 1.07295 50.758 1.38342 52.064C1.69949 53.3938 2.32428 54.7154 3.56383 55.6428C6.02533 57.4841 10.1161 58.7685 14.8212 59.6067C19.5772 60.4538 25.1388 60.8738 30.6831 60.8738C36.2276 60.8738 41.7891 60.4538 46.545 59.6067C51.2504 58.7687 55.3412 57.4842 57.8028 55.6429C59.0424 54.7156 59.6673 53.3938 59.9834 52.064C60.2938 50.7579 60.3285 49.351 60.3285 48.1344V13.8415C60.3285 12.6249 60.2938 11.218 59.9834 9.91171C59.6673 8.58194 59.0423 7.2602 57.8027 6.33294C55.341 4.49168 51.2503 3.20723 46.545 2.36914C41.7891 1.522 36.2276 1.10204 30.6831 1.10205C25.1388 1.10206 19.5772 1.52206 14.8213 2.36923C10.1162 3.20734 6.02543 4.49183 3.5639 6.33314C2.32433 7.26038 1.69951 8.58206 1.38343 9.91181C1.07295 11.2179 1.03845 12.6247 1.03845 13.8411V48.1347Z';
/**
* @typedef { import('diagram-js/lib/util/Types').Dimensions} Dimensions
*/
/**
* @type {Dimensions}
*/
export const DATA_OBJECT_REFERENCE_STANDARD_SIZE = { width: 36, height: 50 };
/**
* @type {Dimensions}
*/
export const DATA_STORE_REFERENCE_STANDARD_SIZE = { width: 50, height: 50 };
/**
* Create a path element with given attributes.
* @param {string} path
* @param {Object} attrs
* @param {Object} OUTLINE_STYLE
* @return {SVGElement}
*/
export function createPath(path, attrs, OUTLINE_STYLE) {
return svgCreate('path', {
d: path,
strokeWidth: 2,
transform: `translate(${attrs.x}, ${attrs.y})`,
...OUTLINE_STYLE
});
}
================================================
FILE: lib/features/outline/index.js
================================================
import Outline from 'diagram-js/lib/features/outline';
import OutlineProvider from './OutlineProvider';
export default {
__depends__: [
Outline
],
__init__: [ 'outlineProvider' ],
outlineProvider: [ 'type', OutlineProvider ]
};
================================================
FILE: lib/features/palette/PaletteProvider.js
================================================
import {
assign
} from 'min-dash';
/**
* @typedef {import('diagram-js/lib/features/palette/Palette').default} Palette
* @typedef {import('diagram-js/lib/features/create/Create').default} Create
* @typedef {import('diagram-js/lib/core/ElementFactory').default} ElementFactory
* @typedef {import('../space-tool/BpmnSpaceTool').default} SpaceTool
* @typedef {import('diagram-js/lib/features/lasso-tool/LassoTool').default} LassoTool
* @typedef {import('diagram-js/lib/features/hand-tool/HandTool').default} HandTool
* @typedef {import('diagram-js/lib/features/global-connect/GlobalConnect').default} GlobalConnect
* @typedef {import('diagram-js/lib/i18n/translate/translate').default} Translate
*
* @typedef {import('diagram-js/lib/features/palette/Palette').PaletteEntries} PaletteEntries
*/
/**
* A palette provider for BPMN 2.0 elements.
*
* @param {Palette} palette
* @param {Create} create
* @param {ElementFactory} elementFactory
* @param {SpaceTool} spaceTool
* @param {LassoTool} lassoTool
* @param {HandTool} handTool
* @param {GlobalConnect} globalConnect
* @param {Translate} translate
*/
export default function PaletteProvider(
palette, create, elementFactory,
spaceTool, lassoTool, handTool,
globalConnect, translate) {
this._palette = palette;
this._create = create;
this._elementFactory = elementFactory;
this._spaceTool = spaceTool;
this._lassoTool = lassoTool;
this._handTool = handTool;
this._globalConnect = globalConnect;
this._translate = translate;
palette.registerProvider(this);
}
PaletteProvider.$inject = [
'palette',
'create',
'elementFactory',
'spaceTool',
'lassoTool',
'handTool',
'globalConnect',
'translate'
];
/**
* @return {PaletteEntries}
*/
PaletteProvider.prototype.getPaletteEntries = function() {
var actions = {},
create = this._create,
elementFactory = this._elementFactory,
spaceTool = this._spaceTool,
lassoTool = this._lassoTool,
handTool = this._handTool,
globalConnect = this._globalConnect,
translate = this._translate;
function createAction(type, group, className, title, options) {
function createListener(event) {
var shape = elementFactory.createShape(assign({ type: type }, options));
create.start(event, shape);
}
return {
group: group,
className: className,
title: title,
action: {
dragstart: createListener,
click: createListener
}
};
}
function createSubprocess(event) {
var subProcess = elementFactory.createShape({
type: 'bpmn:SubProcess',
x: 0,
y: 0,
isExpanded: true
});
var startEvent = elementFactory.createShape({
type: 'bpmn:StartEvent',
x: 40,
y: 82,
parent: subProcess
});
create.start(event, [ subProcess, startEvent ], {
hints: {
autoSelect: [ subProcess ]
}
});
}
function createParticipant(event) {
create.start(event, elementFactory.createParticipantShape());
}
assign(actions, {
'hand-tool': {
group: 'tools',
className: 'bpmn-icon-hand-tool',
title: translate('Activate hand tool'),
action: {
click: function(event) {
handTool.activateHand(event);
}
}
},
'lasso-tool': {
group: 'tools',
className: 'bpmn-icon-lasso-tool',
title: translate('Activate lasso tool'),
action: {
click: function(event) {
lassoTool.activateSelection(event);
}
}
},
'space-tool': {
group: 'tools',
className: 'bpmn-icon-space-tool',
title: translate('Activate create/remove space tool'),
action: {
click: function(event) {
spaceTool.activateSelection(event);
}
}
},
'global-connect-tool': {
group: 'tools',
className: 'bpmn-icon-connection-multi',
title: translate('Activate global connect tool'),
action: {
click: function(event) {
globalConnect.start(event);
}
}
},
'tool-separator': {
group: 'tools',
separator: true
},
'create.start-event': createAction(
'bpmn:StartEvent', 'event', 'bpmn-icon-start-event-none',
translate('Create start event')
),
'create.intermediate-event': createAction(
'bpmn:IntermediateThrowEvent', 'event', 'bpmn-icon-intermediate-event-none',
translate('Create intermediate/boundary event')
),
'create.end-event': createAction(
'bpmn:EndEvent', 'event', 'bpmn-icon-end-event-none',
translate('Create end event')
),
'create.exclusive-gateway': createAction(
'bpmn:ExclusiveGateway', 'gateway', 'bpmn-icon-gateway-none',
translate('Create gateway')
),
'create.task': createAction(
'bpmn:Task', 'activity', 'bpmn-icon-task',
translate('Create task')
),
'create.data-object': createAction(
'bpmn:DataObjectReference', 'data-object', 'bpmn-icon-data-object',
translate('Create data object reference')
),
'create.data-store': createAction(
'bpmn:DataStoreReference', 'data-store', 'bpmn-icon-data-store',
translate('Create data store reference')
),
'create.subprocess-expanded': {
group: 'activity',
className: 'bpmn-icon-subprocess-expanded',
title: translate('Create expanded sub-process'),
action: {
dragstart: createSubprocess,
click: createSubprocess
}
},
'create.participant-expanded': {
group: 'collaboration',
className: 'bpmn-icon-participant',
title: translate('Create pool/participant'),
action: {
dragstart: createParticipant,
click: createParticipant
}
},
'create.group': createAction(
'bpmn:Group', 'artifact', 'bpmn-icon-group',
translate('Create group')
),
});
return actions;
};
================================================
FILE: lib/features/palette/index.js
================================================
import PaletteModule from 'diagram-js/lib/features/palette';
import CreateModule from 'diagram-js/lib/features/create';
import SpaceToolModule from '../space-tool';
import LassoToolModule from 'diagram-js/lib/features/lasso-tool';
import HandToolModule from 'diagram-js/lib/features/hand-tool';
import GlobalConnectModule from 'diagram-js/lib/features/global-connect';
import translate from 'diagram-js/lib/i18n/translate';
import PaletteProvider from './PaletteProvider';
export default {
__depends__: [
PaletteModule,
CreateModule,
SpaceToolModule,
LassoToolModule,
HandToolModule,
GlobalConnectModule,
translate
],
__init__: [ 'paletteProvider' ],
paletteProvider: [ 'type', PaletteProvider ]
};
================================================
FILE: lib/features/popup-menu/ReplaceMenuProvider.js
================================================
import {
getBusinessObject,
is
} from '../../util/ModelUtil';
import {
isEventSubProcess,
isExpanded
} from '../../util/DiUtil';
import {
isDifferentType
} from './util/TypeUtil';
import {
forEach,
filter,
isArray
} from 'min-dash';
import * as replaceOptions from '../replace/ReplaceOptions';
import { canBeNonInterrupting, getInterruptingProperty } from '../modeling/behavior/util/NonInterruptingUtil';
import Icons from './util/Icons';
/**
* @typedef {import('../modeling/BpmnFactory').default} BpmnFactory
* @typedef {import('diagram-js/lib/features/popup-menu/PopupMenu').default} PopupMenu
* @typedef {import('../modeling/Modeling').default} Modeling
* @typedef {import('../replace/BpmnReplace').default} BpmnReplace
* @typedef {import('diagram-js/lib/features/Rules').default} Rules
* @typedef {import('diagram-js/lib/i18n/translate/translate').default} Translate
* @typedef {import('../copy-paste/ModdleCopy').default} ModdleCopy
*
* @typedef {import('../../model/Types').Element} Element
* @typedef {import('../../model/Types').Moddle} Moddle
*
* @typedef {import('diagram-js/lib/features/popup-menu/PopupMenuProvider').PopupMenuEntries} PopupMenuEntries
* @typedef {import('diagram-js/lib/features/popup-menu/PopupMenuProvider').PopupMenuEntry} PopupMenuEntry
* @typedef {import('diagram-js/lib/features/popup-menu/PopupMenuProvider').PopupMenuEntryAction} PopupMenuEntryAction
* @typedef {import('diagram-js/lib/features/popup-menu/PopupMenuProvider').PopupMenuHeaderEntries} PopupMenuHeaderEntries
* @typedef {import('diagram-js/lib/features/popup-menu/PopupMenuProvider').default} PopupMenuProvider
* @typedef {import('diagram-js/lib/features/popup-menu/PopupMenu').PopupMenuTarget} PopupMenuTarget
*
* @typedef {import('./ReplaceOptions').ReplaceOption} ReplaceOption
*/
/**
* A BPMN-specific popup menu provider.
*
* @implements {PopupMenuProvider}
*
* @param {BpmnFactory} bpmnFactory
* @param {PopupMenu} popupMenu
* @param {Modeling} modeling
* @param {Moddle} moddle
* @param {BpmnReplace} bpmnReplace
* @param {Rules} rules
* @param {Translate} translate
* @param {ModdleCopy} moddleCopy
*/
export default function ReplaceMenuProvider(
bpmnFactory, popupMenu, modeling, moddle,
bpmnReplace, rules, translate, moddleCopy) {
this._bpmnFactory = bpmnFactory;
this._popupMenu = popupMenu;
this._modeling = modeling;
this._moddle = moddle;
this._bpmnReplace = bpmnReplace;
this._rules = rules;
this._translate = translate;
this._moddleCopy = moddleCopy;
this._register();
}
ReplaceMenuProvider.$inject = [
'bpmnFactory',
'popupMenu',
'modeling',
'moddle',
'bpmnReplace',
'rules',
'translate',
'moddleCopy'
];
ReplaceMenuProvider.prototype._register = function() {
this._popupMenu.registerProvider('bpmn-replace', this);
};
/**
* @param {PopupMenuTarget} target
*
* @return {PopupMenuEntries}
*/
ReplaceMenuProvider.prototype.getPopupMenuEntries = function(target) {
var businessObject = target.businessObject;
var rules = this._rules;
var sameTypeEventOptions = [],
eventDefinitionType;
var filteredReplaceOptions = [];
if (isArray(target) || !rules.allowed('shape.replace', { element: target })) {
return {};
}
var differentType = isDifferentType(target);
if (is(businessObject, 'bpmn:DataObjectReference')) {
return this._createEntries(target, replaceOptions.DATA_OBJECT_REFERENCE);
}
if (is(businessObject, 'bpmn:DataStoreReference') && !is(target.parent, 'bpmn:Collaboration')) {
return this._createEntries(target, replaceOptions.DATA_STORE_REFERENCE);
}
// typed start, intermediate, and end events
if (is(businessObject, 'bpmn:Event') && !is(businessObject, 'bpmn:BoundaryEvent')) {
eventDefinitionType = businessObject.get('eventDefinitions')[0]?.$type;
sameTypeEventOptions = replaceOptions.TYPED_EVENT[eventDefinitionType] || [];
if (!isEventSubProcess(businessObject.$parent) && is(businessObject.$parent, 'bpmn:SubProcess')) {
sameTypeEventOptions = filter(sameTypeEventOptions, function(option) {
return option.target.type !== 'bpmn:StartEvent';
});
}
}
// start events outside sub processes
if (is(businessObject, 'bpmn:StartEvent') && !is(businessObject.$parent, 'bpmn:SubProcess')) {
filteredReplaceOptions = filter(
replaceOptions.START_EVENT.concat(sameTypeEventOptions),
differentType
);
return this._createEntries(target, filteredReplaceOptions);
}
// expanded/collapsed pools
if (is(businessObject, 'bpmn:Participant')) {
filteredReplaceOptions = filter(replaceOptions.PARTICIPANT, function(replaceOption) {
return isExpanded(target) !== replaceOption.target.isExpanded;
});
return this._createEntries(target, filteredReplaceOptions);
}
// start events inside event sub processes
if (is(businessObject, 'bpmn:StartEvent') && isEventSubProcess(businessObject.$parent)) {
filteredReplaceOptions = filter(
replaceOptions.EVENT_SUB_PROCESS_START_EVENT.concat(sameTypeEventOptions), function(replaceOption) {
var target = replaceOption.target;
var isInterrupting = target.isInterrupting !== false;
var isInterruptingEqual = businessObject.isInterrupting === isInterrupting;
// filters elements which types and event definition are equal but have have different interrupting types
return differentType(replaceOption) || !differentType(replaceOption) && !isInterruptingEqual;
}
);
return this._createEntries(target, filteredReplaceOptions);
}
// start events inside sub processes
if (is(businessObject, 'bpmn:StartEvent') && !isEventSubProcess(businessObject.$parent)
&& is(businessObject.$parent, 'bpmn:SubProcess')) {
filteredReplaceOptions = filter(
replaceOptions.START_EVENT_SUB_PROCESS.concat(sameTypeEventOptions),
differentType
);
return this._createEntries(target, filteredReplaceOptions);
}
// end events
if (is(businessObject, 'bpmn:EndEvent')) {
filteredReplaceOptions = filter(replaceOptions.END_EVENT.concat(sameTypeEventOptions), function(replaceOption) {
var target = replaceOption.target;
// hide cancel end events outside transactions
if (target.eventDefinitionType == 'bpmn:CancelEventDefinition' && !is(businessObject.$parent, 'bpmn:Transaction')) {
return false;
}
return differentType(replaceOption);
});
return this._createEntries(target, filteredReplaceOptions);
}
// boundary events
if (is(businessObject, 'bpmn:BoundaryEvent')) {
filteredReplaceOptions = filter(replaceOptions.BOUNDARY_EVENT, function(replaceOption) {
var target = replaceOption.target;
if (target.eventDefinitionType == 'bpmn:CancelEventDefinition' &&
!is(businessObject.attachedToRef, 'bpmn:Transaction')) {
return false;
}
var cancelActivity = target.cancelActivity !== false;
var isCancelActivityEqual = businessObject.cancelActivity == cancelActivity;
return differentType(replaceOption) || !differentType(replaceOption) && !isCancelActivityEqual;
});
return this._createEntries(target, filteredReplaceOptions);
}
// intermediate events
if (is(businessObject, 'bpmn:IntermediateCatchEvent') ||
is(businessObject, 'bpmn:IntermediateThrowEvent')) {
filteredReplaceOptions = filter(
replaceOptions.INTERMEDIATE_EVENT.concat(sameTypeEventOptions),
differentType
);
return this._createEntries(target, filteredReplaceOptions);
}
// gateways
if (is(businessObject, 'bpmn:Gateway')) {
filteredReplaceOptions = filter(replaceOptions.GATEWAY, differentType);
return this._createEntries(target, filteredReplaceOptions);
}
// transactions
if (is(businessObject, 'bpmn:Transaction')) {
filteredReplaceOptions = filter(replaceOptions.TRANSACTION, differentType);
return this._createEntries(target, filteredReplaceOptions);
}
// expanded event sub processes
if (isEventSubProcess(businessObject) && isExpanded(target)) {
filteredReplaceOptions = filter(replaceOptions.EVENT_SUB_PROCESS, differentType);
return this._createEntries(target, filteredReplaceOptions);
}
// expanded ad hoc sub processes
if (is(businessObject, 'bpmn:AdHocSubProcess') && isExpanded(target)) {
filteredReplaceOptions = filter(replaceOptions.AD_HOC_SUBPROCESS_EXPANDED, differentType);
return this._createEntries(target, filteredReplaceOptions);
}
// expanded sub processes
if (is(businessObject, 'bpmn:SubProcess') && isExpanded(target)) {
filteredReplaceOptions = filter(replaceOptions.SUBPROCESS_EXPANDED, differentType);
return this._createEntries(target, filteredReplaceOptions);
}
// collapsed sub process or collapsed ad hoc sub process
if (is(businessObject, 'bpmn:SubProcess') && !isExpanded(target)) {
filteredReplaceOptions = filter(replaceOptions.TASK, function(replaceOption) {
var isTargetSameType = replaceOption.target.type === target.type;
var isTargetExpanded = replaceOption.target.isExpanded === true;
// Collapsed subprocess cannot be replaced with itself or expanded subprocess of different type.
return isTargetSameType === isTargetExpanded;
});
return this._createEntries(target, filteredReplaceOptions);
}
// sequence flows
if (is(businessObject, 'bpmn:SequenceFlow')) {
return this._createSequenceFlowEntries(target, replaceOptions.SEQUENCE_FLOW);
}
// flow nodes
if (is(businessObject, 'bpmn:FlowNode')) {
filteredReplaceOptions = filter(replaceOptions.TASK, differentType);
return this._createEntries(target, filteredReplaceOptions);
}
return {};
};
/**
* @param {PopupMenuTarget} target
*
* @return {PopupMenuHeaderEntries}
*/
ReplaceMenuProvider.prototype.getPopupMenuHeaderEntries = function(target) {
var headerEntries = {};
if (is(target, 'bpmn:Activity') && !isEventSubProcess(target)) {
headerEntries = {
...headerEntries,
...this._getLoopCharacteristicsHeaderEntries(target)
};
}
if (is(target, 'bpmn:DataObjectReference')) {
headerEntries = {
...headerEntries,
...this._getCollectionHeaderEntries(target)
};
}
if (is(target, 'bpmn:Participant')) {
headerEntries = {
...headerEntries,
...this._getParticipantMultiplicityHeaderEntries(target)
};
}
if (canBeNonInterrupting(target)) {
headerEntries = {
...headerEntries,
...this._getNonInterruptingHeaderEntries(target)
};
}
return headerEntries;
};
/**
* Create popup menu entries for the given target.
*
* @param {PopupMenuTarget} target
* @param {ReplaceOption[]} replaceOptions
*
* @return {PopupMenuEntries}
*/
ReplaceMenuProvider.prototype._createEntries = function(target, replaceOptions) {
var entries = {};
var self = this;
forEach(replaceOptions, function(replaceOption) {
entries[ replaceOption.actionName ] = self._createEntry(replaceOption, target);
});
return entries;
};
/**
* Creates popup menu entries for the given sequence flow.
*
* @param {PopupMenuTarget} target
* @param {ReplaceOption[]} replaceOptions
*
* @return {PopupMenuEntries}
*/
ReplaceMenuProvider.prototype._createSequenceFlowEntries = function(target, replaceOptions) {
var businessObject = getBusinessObject(target);
var entries = {};
var modeling = this._modeling,
moddle = this._moddle;
var self = this;
forEach(replaceOptions, function(replaceOption) {
switch (replaceOption.actionName) {
case 'replace-with-default-flow':
if (businessObject.sourceRef.default !== businessObject &&
(is(businessObject.sourceRef, 'bpmn:ExclusiveGateway') ||
is(businessObject.sourceRef, 'bpmn:InclusiveGateway') ||
is(businessObject.sourceRef, 'bpmn:ComplexGateway') ||
is(businessObject.sourceRef, 'bpmn:Activity'))) {
entries = {
...entries,
[ replaceOption.actionName ]: self._createEntry(replaceOption, target, function() {
modeling.updateProperties(target.source, { default: businessObject });
})
};
}
break;
case 'replace-with-conditional-flow':
if (!businessObject.conditionExpression && is(businessObject.sourceRef, 'bpmn:Activity')) {
entries = {
...entries,
[ replaceOption.actionName ]: self._createEntry(replaceOption, target, function() {
var conditionExpression = moddle.create('bpmn:FormalExpression', { body: '' });
modeling.updateProperties(target, { conditionExpression: conditionExpression });
})
};
}
break;
default:
// conditional flow -> sequence flow
if (is(businessObject.sourceRef, 'bpmn:Activity') && businessObject.conditionExpression) {
entries = {
...entries,
[ replaceOption.actionName ]: self._createEntry(replaceOption, target, function() {
modeling.updateProperties(target, { conditionExpression: undefined });
})
};
}
// default flow -> sequence flow
if ((is(businessObject.sourceRef, 'bpmn:ExclusiveGateway') ||
is(businessObject.sourceRef, 'bpmn:InclusiveGateway') ||
is(businessObject.sourceRef, 'bpmn:ComplexGateway') ||
is(businessObject.sourceRef, 'bpmn:Activity')) &&
businessObject.sourceRef.default === businessObject) {
entries = {
...entries,
[ replaceOption.actionName ]: self._createEntry(replaceOption, target, function() {
modeling.updateProperties(target.source, { default: undefined });
})
};
}
}
});
return entries;
};
/**
* Create a popup menu entry for the given replace option.
*
* @param {ReplaceOption} replaceOption
* @param {PopupMenuTarget} target
* @param {PopupMenuEntryAction} [action]
*
* @return {PopupMenuEntry}
*/
ReplaceMenuProvider.prototype._createEntry = function(replaceOption, target, action) {
var translate = this._translate;
var replaceElement = this._bpmnReplace.replaceElement;
var replaceAction = function() {
return replaceElement(target, replaceOption.target);
};
var label = replaceOption.label;
if (label && typeof label === 'function') {
label = label(target);
}
action = action || replaceAction;
return {
label: translate(label),
className: replaceOption.className,
action: action
};
};
/**
* Get popup menu header entries for the loop characteristics of the given BPMN element.
*
* @param {PopupMenuTarget} target
*
* @return {PopupMenuHeaderEntries}
*/
ReplaceMenuProvider.prototype._getLoopCharacteristicsHeaderEntries = function(target) {
var self = this;
var translate = this._translate;
function toggleLoopCharacteristics(event, entry) {
if (entry.active) {
self._modeling.updateProperties(target, { loopCharacteristics: undefined });
return;
}
var loopCharacteristics = target.businessObject.get('loopCharacteristics');
if (loopCharacteristics && is(loopCharacteristics, entry.options.loopCharacteristics)) {
self._modeling.updateModdleProperties(target, loopCharacteristics, { isSequential: entry.options.isSequential });
} else {
loopCharacteristics = self._moddle.create(entry.options.loopCharacteristics, {
isSequential: entry.options.isSequential
});
self._modeling.updateProperties(target, { loopCharacteristics: loopCharacteristics });
}
}
var businessObject = getBusinessObject(target),
loopCharacteristics = businessObject.loopCharacteristics;
var isSequential,
isLoop,
isParallel;
if (loopCharacteristics) {
isSequential = loopCharacteristics.isSequential;
isLoop = loopCharacteristics.isSequential === undefined;
isParallel = loopCharacteristics.isSequential !== undefined && !loopCharacteristics.isSequential;
}
return {
'toggle-parallel-mi' : {
className: 'bpmn-icon-parallel-mi-marker',
title: translate('Parallel multi-instance'),
active: isParallel,
action: toggleLoopCharacteristics,
options: {
loopCharacteristics: 'bpmn:MultiInstanceLoopCharacteristics',
isSequential: false
}
},
'toggle-sequential-mi': {
className: 'bpmn-icon-sequential-mi-marker',
title: translate('Sequential multi-instance'),
active: isSequential,
action: toggleLoopCharacteristics,
options: {
loopCharacteristics: 'bpmn:MultiInstanceLoopCharacteristics',
isSequential: true
}
},
'toggle-loop': {
className: 'bpmn-icon-loop-marker',
title: translate('Loop'),
active: isLoop,
action: toggleLoopCharacteristics,
options: {
loopCharacteristics: 'bpmn:StandardLoopCharacteristics'
}
}
};
};
/**
* Get popup menu header entries for the collection property of the given BPMN element.
*
* @param {PopupMenuTarget} target
*
* @return {PopupMenuHeaderEntries}
*/
ReplaceMenuProvider.prototype._getCollectionHeaderEntries = function(target) {
var self = this;
var translate = this._translate;
var dataObject = target.businessObject.dataObjectRef;
if (!dataObject) {
return {};
}
function toggleIsCollection(event, entry) {
self._modeling.updateModdleProperties(
target,
dataObject,
{ isCollection: !entry.active });
}
var isCollection = dataObject.isCollection;
return {
'toggle-is-collection': {
className: 'bpmn-icon-parallel-mi-marker',
title: translate('Collection'),
active: isCollection,
action: toggleIsCollection,
}
};
};
/**
* Get popup menu header entries for the participant multiplicity property of the given BPMN element.
*
* @param {PopupMenuTarget} target
*
* @return {PopupMenuHeaderEntries}
*/
ReplaceMenuProvider.prototype._getParticipantMultiplicityHeaderEntries = function(target) {
var self = this;
var bpmnFactory = this._bpmnFactory;
var translate = this._translate;
function toggleParticipantMultiplicity(event, entry) {
var isActive = entry.active;
var participantMultiplicity;
if (!isActive) {
participantMultiplicity = bpmnFactory.create('bpmn:ParticipantMultiplicity');
}
self._modeling.updateProperties(
target,
{ participantMultiplicity: participantMultiplicity });
}
var participantMultiplicity = target.businessObject.participantMultiplicity;
return {
'toggle-participant-multiplicity': {
className: 'bpmn-icon-parallel-mi-marker',
title: translate('Participant multiplicity'),
active: !!participantMultiplicity,
action: toggleParticipantMultiplicity,
}
};
};
ReplaceMenuProvider.prototype._getNonInterruptingHeaderEntries = function(element) {
const translate = this._translate;
const businessObject = getBusinessObject(element);
const self = this;
const interruptingProperty = getInterruptingProperty(element);
const icon = is(element, 'bpmn:BoundaryEvent') ? Icons['intermediate-event-non-interrupting'] : Icons['start-event-non-interrupting'];
const isNonInterrupting = !businessObject[interruptingProperty];
return {
'toggle-non-interrupting': {
imageHtml: icon,
title: translate('Toggle non-interrupting'),
active: isNonInterrupting,
action: function() {
self._modeling.updateProperties(element, {
[interruptingProperty]: !!isNonInterrupting
});
}
}
};
};
================================================
FILE: lib/features/popup-menu/index.js
================================================
import PopupMenuModule from 'diagram-js/lib/features/popup-menu';
import ReplaceModule from '../replace';
import ReplaceMenuProvider from './ReplaceMenuProvider';
import AutoPlaceModule from '../auto-place';
export default {
__depends__: [
PopupMenuModule,
ReplaceModule,
AutoPlaceModule
],
__init__: [
'replaceMenuProvider'
],
replaceMenuProvider: [ 'type', ReplaceMenuProvider ]
};
================================================
FILE: lib/features/popup-menu/util/Icons.js
================================================
export default {
'start-event-non-interrupting': `
`,
'intermediate-event-non-interrupting': `
`
};
================================================
FILE: lib/features/popup-menu/util/TypeUtil.js
================================================
import {
getBusinessObject
} from '../../../util/ModelUtil';
import {
isExpanded
} from '../../../util/DiUtil';
/**
* @typedef {import('../../../model/Types').Element} Element
* @typedef {import('diagram-js/lib/features/popup-menu/PopupMenu').PopupMenuTarget} PopupMenuTarget
*
* @typedef {(entry: PopupMenuTarget) => boolean} DifferentTypeValidator
*/
/**
* Returns true, if an element is from a different type
* than a target definition. Takes into account the type,
* event definition type and triggeredByEvent property.
*
* @param {Element} element
*
* @return {DifferentTypeValidator}
*/
export function isDifferentType(element) {
return function(entry) {
var target = entry.target;
var businessObject = getBusinessObject(element),
eventDefinition = businessObject.eventDefinitions && businessObject.eventDefinitions[0];
var isTypeEqual = businessObject.$type === target.type;
var isEventDefinitionEqual = (
(eventDefinition && eventDefinition.$type) === target.eventDefinitionType
);
var isTriggeredByEventEqual = (
// coherse to
!!target.triggeredByEvent === !!businessObject.triggeredByEvent
);
var isExpandedEqual = (
target.isExpanded === undefined ||
target.isExpanded === isExpanded(element)
);
return !isTypeEqual || !isEventDefinitionEqual || !isTriggeredByEventEqual || !isExpandedEqual;
};
}
================================================
FILE: lib/features/replace/BpmnReplace.js
================================================
import {
pick,
assign,
filter,
forEach,
isArray,
isUndefined,
has
} from 'min-dash';
import {
is,
getDi,
getBusinessObject
} from '../../util/ModelUtil';
import {
isAny
} from '../modeling/util/ModelingUtil';
import {
isExpanded,
isEventSubProcess,
isHorizontal
} from '../../util/DiUtil';
import { getPropertyNames } from '../copy-paste/ModdleCopy';
/**
* @typedef {import('../modeling/BpmnFactory').default} BpmnFactory
* @typedef {import('../modeling/ElementFactory').default} ElementFactory
* @typedef {import('../copy-paste/ModdleCopy').default} ModdleCopy
* @typedef {import('../modeling/Modeling').default} Modeling
* @typedef {import('diagram-js/lib/features/replace/Replace').default} Replace
* @typedef {import('diagram-js/lib/features/rules/Rules').default} Rules
*
* @typedef {import('../../model/Types').Element} Element
* @typedef {import('../../model/Types').Shape} Shape
* @typedef {import('../../model/Types').ModdleElement} ModdleElement
*
* @typedef { {
* type: string;
* cancelActivity: boolean;
* instantiate: boolean;
* eventGatewayType: string;
* triggeredByEvent: boolean;
* isInterrupting: boolean;
* collapsed: boolean;
* isExpanded: boolean;
* eventDefinitionType: string;
* eventDefinitionAttrs: Object;
* host: Shape;
* } } TargetElement
*
* @typedef { {
* moveChildren: boolean;
* } & Record } Hints
*/
function copyProperties(source, target, properties) {
if (!isArray(properties)) {
properties = [ properties ];
}
forEach(properties, function(property) {
if (!isUndefined(source[property])) {
target[property] = source[property];
}
});
}
var CUSTOM_PROPERTIES = [
'cancelActivity',
'instantiate',
'eventGatewayType',
'triggeredByEvent',
'isInterrupting'
];
/**
* Check if element should be collapsed or expanded.
*/
function shouldToggleCollapsed(element, targetElement) {
var oldCollapsed = (
element && has(element, 'collapsed') ? element.collapsed : !isExpanded(element)
);
var targetCollapsed;
if (targetElement && (has(targetElement, 'collapsed') || has(targetElement, 'isExpanded'))) {
// property is explicitly set so use it
targetCollapsed = (
has(targetElement, 'collapsed') ? targetElement.collapsed : !targetElement.isExpanded
);
} else {
// keep old state
targetCollapsed = oldCollapsed;
}
if (oldCollapsed !== targetCollapsed) {
return true;
}
return false;
}
/**
* BPMN-specific replace.
*
* @param {BpmnFactory} bpmnFactory
* @param {ElementFactory} elementFactory
* @param {ModdleCopy} moddleCopy
* @param {Modeling} modeling
* @param {Replace} replace
* @param {Rules} rules
*/
export default function BpmnReplace(
bpmnFactory,
elementFactory,
moddleCopy,
modeling,
replace,
rules
) {
/**
* Prepares a new business object for the replacement element
* and triggers the replace operation.
*
* @param {Element} element
* @param {TargetElement} targetElement
* @param {Hints} [hints]
*
* @return {Element}
*/
function replaceElement(element, targetElement, hints) {
hints = hints || {};
var type = targetElement.type,
oldBusinessObject = element.businessObject;
if (isSubProcess(oldBusinessObject) && (type === 'bpmn:SubProcess' || type === 'bpmn:AdHocSubProcess')) {
if (shouldToggleCollapsed(element, targetElement)) {
// expanding or collapsing process
modeling.toggleCollapse(element);
return element;
}
}
var newBusinessObject = bpmnFactory.create(type);
var newElement = {
type: type,
businessObject: newBusinessObject,
};
newElement.di = {};
if (type === 'bpmn:ExclusiveGateway') {
newElement.di.isMarkerVisible = true;
}
// colors will be set to DI
copyProperties(element.di, newElement.di, [
'fill',
'stroke',
'background-color',
'border-color',
'color'
]);
var elementProps = getPropertyNames(oldBusinessObject.$descriptor),
newElementProps = getPropertyNames(newBusinessObject.$descriptor, true),
copyProps = intersection(elementProps, newElementProps);
// initialize special properties defined in target definition
assign(newBusinessObject, pick(targetElement, CUSTOM_PROPERTIES));
var properties = filter(copyProps, function(propertyName) {
// copying event definitions, unless we replace
if (propertyName === 'eventDefinitions') {
return hasEventDefinition(element, targetElement.eventDefinitionType);
}
// retain loop characteristics if the target element
// is not an event sub process
if (propertyName === 'loopCharacteristics') {
return !isEventSubProcess(newBusinessObject);
}
// so the applied properties from 'target' don't get lost
if (has(newBusinessObject, propertyName)) {
return false;
}
if (propertyName === 'processRef' && targetElement.isExpanded === false) {
return false;
}
if (propertyName === 'triggeredByEvent') {
return false;
}
if (propertyName === 'isForCompensation') {
return !isEventSubProcess(newBusinessObject);
}
return true;
});
newBusinessObject = moddleCopy.copyElement(
oldBusinessObject,
newBusinessObject,
properties
);
// initialize custom BPMN extensions
if (targetElement.eventDefinitionType) {
// only initialize with new eventDefinition
// if we did not set an event definition yet,
// i.e. because we copied it
if (!hasEventDefinition(newBusinessObject, targetElement.eventDefinitionType)) {
newElement.eventDefinitionType = targetElement.eventDefinitionType;
newElement.eventDefinitionAttrs = targetElement.eventDefinitionAttrs;
}
}
if (is(oldBusinessObject, 'bpmn:Activity')) {
if (isSubProcess(oldBusinessObject)) {
// no toggeling, so keep old state
newElement.isExpanded = isExpanded(element);
}
// else if property is explicitly set, use it
else if (targetElement && has(targetElement, 'isExpanded')) {
newElement.isExpanded = targetElement.isExpanded;
// assign default size of new expanded element
var defaultSize = elementFactory.getDefaultSize(newBusinessObject, {
isExpanded: newElement.isExpanded
});
newElement.width = defaultSize.width;
newElement.height = defaultSize.height;
// keep element centered
newElement.x = element.x - (newElement.width - element.width) / 2;
newElement.y = element.y - (newElement.height - element.height) / 2;
}
// TODO: need also to respect min/max Size
// copy size, from an expanded subprocess to an expanded alternative subprocess
// except bpmn:Task, because Task is always expanded
if ((isExpanded(element) && !is(oldBusinessObject, 'bpmn:Task')) && newElement.isExpanded) {
newElement.width = element.width;
newElement.height = element.height;
}
}
// remove children if not expanding sub process
if (isSubProcess(oldBusinessObject) && !isSubProcess(newBusinessObject)) {
hints.moveChildren = false;
}
// transform collapsed/expanded pools
if (is(oldBusinessObject, 'bpmn:Participant')) {
// create expanded pool
if (targetElement.isExpanded === true) {
newBusinessObject.processRef = bpmnFactory.create('bpmn:Process');
} else {
// remove children when transforming to collapsed pool
hints.moveChildren = false;
}
// apply same directionality
var isHorizontalPool = isHorizontal(element);
if (!getDi(element).isHorizontal) {
getDi(newElement).isHorizontal = isHorizontalPool;
}
// keep the existing size of the pool's direction to
// prevent dangling message flows
newElement.width = isHorizontalPool ? element.width : elementFactory.getDefaultSize(newElement).width;
newElement.height = isHorizontalPool ? elementFactory.getDefaultSize(newElement).height : element.height;
}
if (!rules.allowed('shape.resize', { shape: newBusinessObject })) {
newElement.height = elementFactory.getDefaultSize(newElement).height;
newElement.width = elementFactory.getDefaultSize(newElement).width;
}
newBusinessObject.name = oldBusinessObject.name;
// retain default flow's reference between inclusive <-> exclusive gateways and activities
if (
isAny(oldBusinessObject, [
'bpmn:ExclusiveGateway',
'bpmn:InclusiveGateway',
'bpmn:Activity'
]) &&
isAny(newBusinessObject, [
'bpmn:ExclusiveGateway',
'bpmn:InclusiveGateway',
'bpmn:Activity'
])
) {
newBusinessObject.default = oldBusinessObject.default;
}
if (
targetElement.host &&
!is(oldBusinessObject, 'bpmn:BoundaryEvent') &&
is(newBusinessObject, 'bpmn:BoundaryEvent')
) {
newElement.host = targetElement.host;
}
// The DataStoreReference element is 14px wider than the DataObjectReference element
// This ensures that they stay centered on the x axis when replaced
if (
newElement.type === 'bpmn:DataStoreReference' ||
newElement.type === 'bpmn:DataObjectReference'
) {
newElement.x = element.x + (element.width - newElement.width) / 2;
}
return replace.replaceElement(element, newElement, { ...hints, targetElement });
}
this.replaceElement = replaceElement;
}
BpmnReplace.$inject = [
'bpmnFactory',
'elementFactory',
'moddleCopy',
'modeling',
'replace',
'rules'
];
/**
* @param {ModdleElement} businessObject
*
* @return {boolean}
*/
function isSubProcess(businessObject) {
return is(businessObject, 'bpmn:SubProcess');
}
/**
* @param {Element|ModdleElement} element
* @param {string} type
*
* @return {boolean}
*/
function hasEventDefinition(element, type) {
var businessObject = getBusinessObject(element);
return type && businessObject.get('eventDefinitions').some(function(definition) {
return is(definition, type);
});
}
/**
* Compute intersection between two arrays.
*
* @param {Array} a
* @param {Array} b
*
* @return {Array}
*/
function intersection(a, b) {
return a.filter(function(item) {
return b.includes(item);
});
}
================================================
FILE: lib/features/replace/ReplaceOptions.js
================================================
/**
* @typedef { () => string } LabelGetter
*
* @typedef { {
* label: string | LabelGetter;
* actionName: string;
* className: string;
* target?: {
* type: string;
* isExpanded?: boolean;
* isInterrupting?: boolean;
* triggeredByEvent?: boolean;
* cancelActivity?: boolean;
* eventDefinitionType?: string;
* eventDefinitionAttrs?: Record
* };
* } } ReplaceOption
*/
/**
* @type {ReplaceOption[]}
*/
export var START_EVENT = [
{
label: 'Start event',
actionName: 'replace-with-none-start',
className: 'bpmn-icon-start-event-none',
target: {
type: 'bpmn:StartEvent'
}
},
{
label: 'Intermediate throw event',
actionName: 'replace-with-none-intermediate-throwing',
className: 'bpmn-icon-intermediate-event-none',
target: {
type: 'bpmn:IntermediateThrowEvent'
}
},
{
label: 'End event',
actionName: 'replace-with-none-end',
className: 'bpmn-icon-end-event-none',
target: {
type: 'bpmn:EndEvent'
}
},
{
label: 'Message start event',
actionName: 'replace-with-message-start',
className: 'bpmn-icon-start-event-message',
target: {
type: 'bpmn:StartEvent',
eventDefinitionType: 'bpmn:MessageEventDefinition'
}
},
{
label: 'Timer start event',
actionName: 'replace-with-timer-start',
className: 'bpmn-icon-start-event-timer',
target: {
type: 'bpmn:StartEvent',
eventDefinitionType: 'bpmn:TimerEventDefinition'
}
},
{
label: 'Conditional start event',
actionName: 'replace-with-conditional-start',
className: 'bpmn-icon-start-event-condition',
target: {
type: 'bpmn:StartEvent',
eventDefinitionType: 'bpmn:ConditionalEventDefinition'
}
},
{
label: 'Signal start event',
actionName: 'replace-with-signal-start',
className: 'bpmn-icon-start-event-signal',
target: {
type: 'bpmn:StartEvent',
eventDefinitionType: 'bpmn:SignalEventDefinition'
}
}
];
/**
* @type {ReplaceOption[]}
*/
export var START_EVENT_SUB_PROCESS = [
{
label: 'Start event',
actionName: 'replace-with-none-start',
className: 'bpmn-icon-start-event-none',
target: {
type: 'bpmn:StartEvent'
}
},
{
label: 'Intermediate throw event',
actionName: 'replace-with-none-intermediate-throwing',
className: 'bpmn-icon-intermediate-event-none',
target: {
type: 'bpmn:IntermediateThrowEvent'
}
},
{
label: 'End event',
actionName: 'replace-with-none-end',
className: 'bpmn-icon-end-event-none',
target: {
type: 'bpmn:EndEvent'
}
}
];
/**
* @type {ReplaceOption[]}
*/
export var INTERMEDIATE_EVENT = [
{
label: 'Start event',
actionName: 'replace-with-none-start',
className: 'bpmn-icon-start-event-none',
target: {
type: 'bpmn:StartEvent'
}
},
{
label: 'Intermediate throw event',
actionName: 'replace-with-none-intermediate-throw',
className: 'bpmn-icon-intermediate-event-none',
target: {
type: 'bpmn:IntermediateThrowEvent'
}
},
{
label: 'End event',
actionName: 'replace-with-none-end',
className: 'bpmn-icon-end-event-none',
target: {
type: 'bpmn:EndEvent'
}
},
{
label: 'Message intermediate catch event',
actionName: 'replace-with-message-intermediate-catch',
className: 'bpmn-icon-intermediate-event-catch-message',
target: {
type: 'bpmn:IntermediateCatchEvent',
eventDefinitionType: 'bpmn:MessageEventDefinition'
}
},
{
label: 'Message intermediate throw event',
actionName: 'replace-with-message-intermediate-throw',
className: 'bpmn-icon-intermediate-event-throw-message',
target: {
type: 'bpmn:IntermediateThrowEvent',
eventDefinitionType: 'bpmn:MessageEventDefinition'
}
},
{
label: 'Timer intermediate catch event',
actionName: 'replace-with-timer-intermediate-catch',
className: 'bpmn-icon-intermediate-event-catch-timer',
target: {
type: 'bpmn:IntermediateCatchEvent',
eventDefinitionType: 'bpmn:TimerEventDefinition'
}
},
{
label: 'Escalation intermediate throw event',
actionName: 'replace-with-escalation-intermediate-throw',
className: 'bpmn-icon-intermediate-event-throw-escalation',
target: {
type: 'bpmn:IntermediateThrowEvent',
eventDefinitionType: 'bpmn:EscalationEventDefinition'
}
},
{
label: 'Conditional intermediate catch event',
actionName: 'replace-with-conditional-intermediate-catch',
className: 'bpmn-icon-intermediate-event-catch-condition',
target: {
type: 'bpmn:IntermediateCatchEvent',
eventDefinitionType: 'bpmn:ConditionalEventDefinition'
}
},
{
label: 'Link intermediate catch event',
actionName: 'replace-with-link-intermediate-catch',
className: 'bpmn-icon-intermediate-event-catch-link',
target: {
type: 'bpmn:IntermediateCatchEvent',
eventDefinitionType: 'bpmn:LinkEventDefinition',
eventDefinitionAttrs: {
name: ''
}
}
},
{
label: 'Link intermediate throw event',
actionName: 'replace-with-link-intermediate-throw',
className: 'bpmn-icon-intermediate-event-throw-link',
target: {
type: 'bpmn:IntermediateThrowEvent',
eventDefinitionType: 'bpmn:LinkEventDefinition',
eventDefinitionAttrs: {
name: ''
}
}
},
{
label: 'Compensation intermediate throw event',
actionName: 'replace-with-compensation-intermediate-throw',
className: 'bpmn-icon-intermediate-event-throw-compensation',
target: {
type: 'bpmn:IntermediateThrowEvent',
eventDefinitionType: 'bpmn:CompensateEventDefinition'
}
},
{
label: 'Signal intermediate catch event',
actionName: 'replace-with-signal-intermediate-catch',
className: 'bpmn-icon-intermediate-event-catch-signal',
target: {
type: 'bpmn:IntermediateCatchEvent',
eventDefinitionType: 'bpmn:SignalEventDefinition'
}
},
{
label: 'Signal intermediate throw event',
actionName: 'replace-with-signal-intermediate-throw',
className: 'bpmn-icon-intermediate-event-throw-signal',
target: {
type: 'bpmn:IntermediateThrowEvent',
eventDefinitionType: 'bpmn:SignalEventDefinition'
}
}
];
/**
* @type {ReplaceOption[]}
*/
export var END_EVENT = [
{
label: 'Start event',
actionName: 'replace-with-none-start',
className: 'bpmn-icon-start-event-none',
target: {
type: 'bpmn:StartEvent'
}
},
{
label: 'Intermediate throw event',
actionName: 'replace-with-none-intermediate-throw',
className: 'bpmn-icon-intermediate-event-none',
target: {
type: 'bpmn:IntermediateThrowEvent'
}
},
{
label: 'End event',
actionName: 'replace-with-none-end',
className: 'bpmn-icon-end-event-none',
target: {
type: 'bpmn:EndEvent'
}
},
{
label: 'Message end event',
actionName: 'replace-with-message-end',
className: 'bpmn-icon-end-event-message',
target: {
type: 'bpmn:EndEvent',
eventDefinitionType: 'bpmn:MessageEventDefinition'
}
},
{
label: 'Escalation end event',
actionName: 'replace-with-escalation-end',
className: 'bpmn-icon-end-event-escalation',
target: {
type: 'bpmn:EndEvent',
eventDefinitionType: 'bpmn:EscalationEventDefinition'
}
},
{
label: 'Error end event',
actionName: 'replace-with-error-end',
className: 'bpmn-icon-end-event-error',
target: {
type: 'bpmn:EndEvent',
eventDefinitionType: 'bpmn:ErrorEventDefinition'
}
},
{
label: 'Cancel end event',
actionName: 'replace-with-cancel-end',
className: 'bpmn-icon-end-event-cancel',
target: {
type: 'bpmn:EndEvent',
eventDefinitionType: 'bpmn:CancelEventDefinition'
}
},
{
label: 'Compensation end event',
actionName: 'replace-with-compensation-end',
className: 'bpmn-icon-end-event-compensation',
target: {
type: 'bpmn:EndEvent',
eventDefinitionType: 'bpmn:CompensateEventDefinition'
}
},
{
label: 'Signal end event',
actionName: 'replace-with-signal-end',
className: 'bpmn-icon-end-event-signal',
target: {
type: 'bpmn:EndEvent',
eventDefinitionType: 'bpmn:SignalEventDefinition'
}
},
{
label: 'Terminate end event',
actionName: 'replace-with-terminate-end',
className: 'bpmn-icon-end-event-terminate',
target: {
type: 'bpmn:EndEvent',
eventDefinitionType: 'bpmn:TerminateEventDefinition'
}
}
];
/**
* @type {ReplaceOption[]}
*/
export var GATEWAY = [
{
label: 'Exclusive gateway',
actionName: 'replace-with-exclusive-gateway',
className: 'bpmn-icon-gateway-xor',
target: {
type: 'bpmn:ExclusiveGateway'
}
},
{
label: 'Parallel gateway',
actionName: 'replace-with-parallel-gateway',
className: 'bpmn-icon-gateway-parallel',
target: {
type: 'bpmn:ParallelGateway'
}
},
{
label: 'Inclusive gateway',
actionName: 'replace-with-inclusive-gateway',
className: 'bpmn-icon-gateway-or',
target: {
type: 'bpmn:InclusiveGateway'
}
},
{
label: 'Complex gateway',
actionName: 'replace-with-complex-gateway',
className: 'bpmn-icon-gateway-complex',
target: {
type: 'bpmn:ComplexGateway'
}
},
{
label: 'Event-based gateway',
actionName: 'replace-with-event-based-gateway',
className: 'bpmn-icon-gateway-eventbased',
target: {
type: 'bpmn:EventBasedGateway',
instantiate: false,
eventGatewayType: 'Exclusive'
}
}
// Gateways deactivated until https://github.com/bpmn-io/bpmn-js/issues/194
// {
// label: 'Event based instantiating Gateway',
// actionName: 'replace-with-exclusive-event-based-gateway',
// className: 'bpmn-icon-exclusive-event-based',
// target: {
// type: 'bpmn:EventBasedGateway'
// },
// options: {
// businessObject: { instantiate: true, eventGatewayType: 'Exclusive' }
// }
// },
// {
// label: 'Parallel Event based instantiating Gateway',
// actionName: 'replace-with-parallel-event-based-instantiate-gateway',
// className: 'bpmn-icon-parallel-event-based-instantiate-gateway',
// target: {
// type: 'bpmn:EventBasedGateway'
// },
// options: {
// businessObject: { instantiate: true, eventGatewayType: 'Parallel' }
// }
// }
];
/**
* @type {ReplaceOption[]}
*/
export var SUBPROCESS_EXPANDED = [
{
label: 'Transaction',
actionName: 'replace-with-transaction',
className: 'bpmn-icon-transaction',
target: {
type: 'bpmn:Transaction',
isExpanded: true
}
},
{
label: 'Event sub-process',
actionName: 'replace-with-event-subprocess',
className: 'bpmn-icon-event-subprocess-expanded',
target: {
type: 'bpmn:SubProcess',
triggeredByEvent: true,
isExpanded: true
}
},
{
label: 'Ad-hoc sub-process',
actionName: 'replace-with-ad-hoc-subprocess',
className: 'bpmn-icon-subprocess-expanded',
target: {
type: 'bpmn:AdHocSubProcess',
isExpanded: true
}
},
{
label: 'Sub-process (collapsed)',
actionName: 'replace-with-collapsed-subprocess',
className: 'bpmn-icon-subprocess-collapsed',
target: {
type: 'bpmn:SubProcess',
isExpanded: false
}
}
];
/**
* @type {ReplaceOption[]}
*/
export var AD_HOC_SUBPROCESS_EXPANDED = [
{
label: 'Sub-process',
actionName: 'replace-with-subprocess',
className: 'bpmn-icon-subprocess-expanded',
target: {
type: 'bpmn:SubProcess',
isExpanded: true
}
},
{
label: 'Transaction',
actionName: 'replace-with-transaction',
className: 'bpmn-icon-transaction',
target: {
type: 'bpmn:Transaction',
isExpanded: true
}
},
{
label: 'Event sub-process',
actionName: 'replace-with-event-subprocess',
className: 'bpmn-icon-event-subprocess-expanded',
target: {
type: 'bpmn:SubProcess',
triggeredByEvent: true,
isExpanded: true
}
},
{
label: 'Ad-hoc sub-process (collapsed)',
actionName: 'replace-with-collapsed-ad-hoc-subprocess',
className: 'bpmn-icon-subprocess-collapsed',
target: {
type: 'bpmn:AdHocSubProcess',
isExpanded: false
}
}
];
/**
* @type {ReplaceOption[]}
*/
export var TRANSACTION = [
{
label: 'Transaction',
actionName: 'replace-with-transaction',
className: 'bpmn-icon-transaction',
target: {
type: 'bpmn:Transaction',
isExpanded: true
}
},
{
label: 'Sub-process',
actionName: 'replace-with-subprocess',
className: 'bpmn-icon-subprocess-expanded',
target: {
type: 'bpmn:SubProcess',
isExpanded: true
}
},
{
label: 'Ad-hoc sub-process',
actionName: 'replace-with-ad-hoc-subprocess',
className: 'bpmn-icon-subprocess-expanded',
target: {
type: 'bpmn:AdHocSubProcess',
isExpanded: true
}
},
{
label: 'Event sub-process',
actionName: 'replace-with-event-subprocess',
className: 'bpmn-icon-event-subprocess-expanded',
target: {
type: 'bpmn:SubProcess',
triggeredByEvent: true,
isExpanded: true
}
}
];
/**
* @type {ReplaceOption[]}
*/
export var EVENT_SUB_PROCESS = TRANSACTION;
/**
* @type {ReplaceOption[]}
*/
export var TASK = [
{
label: 'Task',
actionName: 'replace-with-task',
className: 'bpmn-icon-task',
target: {
type: 'bpmn:Task'
}
},
{
label: 'User task',
actionName: 'replace-with-user-task',
className: 'bpmn-icon-user',
target: {
type: 'bpmn:UserTask'
}
},
{
label: 'Service task',
actionName: 'replace-with-service-task',
className: 'bpmn-icon-service',
target: {
type: 'bpmn:ServiceTask'
}
},
{
label: 'Send task',
actionName: 'replace-with-send-task',
className: 'bpmn-icon-send',
target: {
type: 'bpmn:SendTask'
}
},
{
label: 'Receive task',
actionName: 'replace-with-receive-task',
className: 'bpmn-icon-receive',
target: {
type: 'bpmn:ReceiveTask'
}
},
{
label: 'Manual task',
actionName: 'replace-with-manual-task',
className: 'bpmn-icon-manual',
target: {
type: 'bpmn:ManualTask'
}
},
{
label: 'Business rule task',
actionName: 'replace-with-rule-task',
className: 'bpmn-icon-business-rule',
target: {
type: 'bpmn:BusinessRuleTask'
}
},
{
label: 'Script task',
actionName: 'replace-with-script-task',
className: 'bpmn-icon-script',
target: {
type: 'bpmn:ScriptTask'
}
},
{
label: 'Call activity',
actionName: 'replace-with-call-activity',
className: 'bpmn-icon-call-activity',
target: {
type: 'bpmn:CallActivity'
}
},
{
label: 'Sub-process (collapsed)',
actionName: 'replace-with-collapsed-subprocess',
className: 'bpmn-icon-subprocess-collapsed',
target: {
type: 'bpmn:SubProcess',
isExpanded: false
}
},
{
label: 'Sub-process (expanded)',
actionName: 'replace-with-expanded-subprocess',
className: 'bpmn-icon-subprocess-expanded',
target: {
type: 'bpmn:SubProcess',
isExpanded: true
}
},
{
label: 'Ad-hoc sub-process (collapsed)',
actionName: 'replace-with-collapsed-ad-hoc-subprocess',
className: 'bpmn-icon-subprocess-collapsed',
target: {
type: 'bpmn:AdHocSubProcess',
isExpanded: false
}
},
{
label: 'Ad-hoc sub-process (expanded)',
actionName: 'replace-with-ad-hoc-subprocess',
className: 'bpmn-icon-subprocess-expanded',
target: {
type: 'bpmn:AdHocSubProcess',
isExpanded: true
}
}
];
/**
* @type {ReplaceOption[]}
*/
export var DATA_OBJECT_REFERENCE = [
{
label: 'Data store reference',
actionName: 'replace-with-data-store-reference',
className: 'bpmn-icon-data-store',
target: {
type: 'bpmn:DataStoreReference'
}
}
];
/**
* @type {ReplaceOption[]}
*/
export var DATA_STORE_REFERENCE = [
{
label: 'Data object reference',
actionName: 'replace-with-data-object-reference',
className: 'bpmn-icon-data-object',
target: {
type: 'bpmn:DataObjectReference'
}
}
];
/**
* @type {ReplaceOption[]}
*/
export var BOUNDARY_EVENT = [
{
label: 'Message boundary event',
actionName: 'replace-with-message-boundary',
className: 'bpmn-icon-intermediate-event-catch-message',
target: {
type: 'bpmn:BoundaryEvent',
eventDefinitionType: 'bpmn:MessageEventDefinition',
cancelActivity: true
}
},
{
label: 'Timer boundary event',
actionName: 'replace-with-timer-boundary',
className: 'bpmn-icon-intermediate-event-catch-timer',
target: {
type: 'bpmn:BoundaryEvent',
eventDefinitionType: 'bpmn:TimerEventDefinition',
cancelActivity: true
}
},
{
label: 'Escalation boundary event',
actionName: 'replace-with-escalation-boundary',
className: 'bpmn-icon-intermediate-event-catch-escalation',
target: {
type: 'bpmn:BoundaryEvent',
eventDefinitionType: 'bpmn:EscalationEventDefinition',
cancelActivity: true
}
},
{
label: 'Conditional boundary event',
actionName: 'replace-with-conditional-boundary',
className: 'bpmn-icon-intermediate-event-catch-condition',
target: {
type: 'bpmn:BoundaryEvent',
eventDefinitionType: 'bpmn:ConditionalEventDefinition',
cancelActivity: true
}
},
{
label: 'Error boundary event',
actionName: 'replace-with-error-boundary',
className: 'bpmn-icon-intermediate-event-catch-error',
target: {
type: 'bpmn:BoundaryEvent',
eventDefinitionType: 'bpmn:ErrorEventDefinition',
cancelActivity: true
}
},
{
label: 'Cancel boundary event',
actionName: 'replace-with-cancel-boundary',
className: 'bpmn-icon-intermediate-event-catch-cancel',
target: {
type: 'bpmn:BoundaryEvent',
eventDefinitionType: 'bpmn:CancelEventDefinition',
cancelActivity: true
}
},
{
label: 'Signal boundary event',
actionName: 'replace-with-signal-boundary',
className: 'bpmn-icon-intermediate-event-catch-signal',
target: {
type: 'bpmn:BoundaryEvent',
eventDefinitionType: 'bpmn:SignalEventDefinition',
cancelActivity: true
}
},
{
label: 'Compensation boundary event',
actionName: 'replace-with-compensation-boundary',
className: 'bpmn-icon-intermediate-event-catch-compensation',
target: {
type: 'bpmn:BoundaryEvent',
eventDefinitionType: 'bpmn:CompensateEventDefinition',
cancelActivity: true
}
},
{
label: 'Message boundary event (non-interrupting)',
actionName: 'replace-with-non-interrupting-message-boundary',
className: 'bpmn-icon-intermediate-event-catch-non-interrupting-message',
target: {
type: 'bpmn:BoundaryEvent',
eventDefinitionType: 'bpmn:MessageEventDefinition',
cancelActivity: false
}
},
{
label: 'Timer boundary event (non-interrupting)',
actionName: 'replace-with-non-interrupting-timer-boundary',
className: 'bpmn-icon-intermediate-event-catch-non-interrupting-timer',
target: {
type: 'bpmn:BoundaryEvent',
eventDefinitionType: 'bpmn:TimerEventDefinition',
cancelActivity: false
}
},
{
label: 'Escalation boundary event (non-interrupting)',
actionName: 'replace-with-non-interrupting-escalation-boundary',
className: 'bpmn-icon-intermediate-event-catch-non-interrupting-escalation',
target: {
type: 'bpmn:BoundaryEvent',
eventDefinitionType: 'bpmn:EscalationEventDefinition',
cancelActivity: false
}
},
{
label: 'Conditional boundary event (non-interrupting)',
actionName: 'replace-with-non-interrupting-conditional-boundary',
className: 'bpmn-icon-intermediate-event-catch-non-interrupting-condition',
target: {
type: 'bpmn:BoundaryEvent',
eventDefinitionType: 'bpmn:ConditionalEventDefinition',
cancelActivity: false
}
},
{
label: 'Signal boundary event (non-interrupting)',
actionName: 'replace-with-non-interrupting-signal-boundary',
className: 'bpmn-icon-intermediate-event-catch-non-interrupting-signal',
target: {
type: 'bpmn:BoundaryEvent',
eventDefinitionType: 'bpmn:SignalEventDefinition',
cancelActivity: false
}
}
];
/**
* @type {ReplaceOption[]}
*/
export var EVENT_SUB_PROCESS_START_EVENT = [
{
label: 'Message start event',
actionName: 'replace-with-message-start',
className: 'bpmn-icon-start-event-message',
target: {
type: 'bpmn:StartEvent',
eventDefinitionType: 'bpmn:MessageEventDefinition',
isInterrupting: true
}
},
{
label: 'Timer start event',
actionName: 'replace-with-timer-start',
className: 'bpmn-icon-start-event-timer',
target: {
type: 'bpmn:StartEvent',
eventDefinitionType: 'bpmn:TimerEventDefinition',
isInterrupting: true
}
},
{
label: 'Conditional start event',
actionName: 'replace-with-conditional-start',
className: 'bpmn-icon-start-event-condition',
target: {
type: 'bpmn:StartEvent',
eventDefinitionType: 'bpmn:ConditionalEventDefinition',
isInterrupting: true
}
},
{
label: 'Signal start event',
actionName: 'replace-with-signal-start',
className: 'bpmn-icon-start-event-signal',
target: {
type: 'bpmn:StartEvent',
eventDefinitionType: 'bpmn:SignalEventDefinition',
isInterrupting: true
}
},
{
label: 'Error start event',
actionName: 'replace-with-error-start',
className: 'bpmn-icon-start-event-error',
target: {
type: 'bpmn:StartEvent',
eventDefinitionType: 'bpmn:ErrorEventDefinition',
isInterrupting: true
}
},
{
label: 'Escalation start event',
actionName: 'replace-with-escalation-start',
className: 'bpmn-icon-start-event-escalation',
target: {
type: 'bpmn:StartEvent',
eventDefinitionType: 'bpmn:EscalationEventDefinition',
isInterrupting: true
}
},
{
label: 'Compensation start event',
actionName: 'replace-with-compensation-start',
className: 'bpmn-icon-start-event-compensation',
target: {
type: 'bpmn:StartEvent',
eventDefinitionType: 'bpmn:CompensateEventDefinition',
isInterrupting: true
}
},
{
label: 'Message start event (non-interrupting)',
actionName: 'replace-with-non-interrupting-message-start',
className: 'bpmn-icon-start-event-non-interrupting-message',
target: {
type: 'bpmn:StartEvent',
eventDefinitionType: 'bpmn:MessageEventDefinition',
isInterrupting: false
}
},
{
label: 'Timer start event (non-interrupting)',
actionName: 'replace-with-non-interrupting-timer-start',
className: 'bpmn-icon-start-event-non-interrupting-timer',
target: {
type: 'bpmn:StartEvent',
eventDefinitionType: 'bpmn:TimerEventDefinition',
isInterrupting: false
}
},
{
label: 'Conditional start event (non-interrupting)',
actionName: 'replace-with-non-interrupting-conditional-start',
className: 'bpmn-icon-start-event-non-interrupting-condition',
target: {
type: 'bpmn:StartEvent',
eventDefinitionType: 'bpmn:ConditionalEventDefinition',
isInterrupting: false
}
},
{
label: 'Signal start event (non-interrupting)',
actionName: 'replace-with-non-interrupting-signal-start',
className: 'bpmn-icon-start-event-non-interrupting-signal',
target: {
type: 'bpmn:StartEvent',
eventDefinitionType: 'bpmn:SignalEventDefinition',
isInterrupting: false
}
},
{
label: 'Escalation start event (non-interrupting)',
actionName: 'replace-with-non-interrupting-escalation-start',
className: 'bpmn-icon-start-event-non-interrupting-escalation',
target: {
type: 'bpmn:StartEvent',
eventDefinitionType: 'bpmn:EscalationEventDefinition',
isInterrupting: false
}
}
];
/**
* @type {ReplaceOption[]}
*/
export var SEQUENCE_FLOW = [
{
label: 'Sequence flow',
actionName: 'replace-with-sequence-flow',
className: 'bpmn-icon-connection'
},
{
label: 'Default flow',
actionName: 'replace-with-default-flow',
className: 'bpmn-icon-default-flow'
},
{
label: 'Conditional flow',
actionName: 'replace-with-conditional-flow',
className: 'bpmn-icon-conditional-flow'
}
];
/**
* @type {ReplaceOption[]}
*/
export var PARTICIPANT = [
{
label: 'Expanded pool/participant',
actionName: 'replace-with-expanded-pool',
className: 'bpmn-icon-participant',
target: {
type: 'bpmn:Participant',
isExpanded: true
}
},
{
label: function(element) {
var label = 'Empty pool/participant';
if (element.children && element.children.length) {
label += ' (removes content)';
}
return label;
},
actionName: 'replace-with-collapsed-pool',
// TODO(@janstuemmel): maybe design new icon
className: 'bpmn-icon-lane',
target: {
type: 'bpmn:Participant',
isExpanded: false
}
}
];
/**
* @type {{ [key: string]: ReplaceOption[]}}
*/
export var TYPED_EVENT = {
'bpmn:MessageEventDefinition': [
{
label: 'Message start event',
actionName: 'replace-with-message-start',
className: 'bpmn-icon-start-event-message',
target: {
type: 'bpmn:StartEvent',
eventDefinitionType: 'bpmn:MessageEventDefinition',
isInterrupting: true
}
},
{
label: 'Message intermediate catch event',
actionName: 'replace-with-message-intermediate-catch',
className: 'bpmn-icon-intermediate-event-catch-message',
target: {
type: 'bpmn:IntermediateCatchEvent',
eventDefinitionType: 'bpmn:MessageEventDefinition'
}
},
{
label: 'Message intermediate throw event',
actionName: 'replace-with-message-intermediate-throw',
className: 'bpmn-icon-intermediate-event-throw-message',
target: {
type: 'bpmn:IntermediateThrowEvent',
eventDefinitionType: 'bpmn:MessageEventDefinition'
}
},
{
label: 'Message end event',
actionName: 'replace-with-message-end',
className: 'bpmn-icon-end-event-message',
target: {
type: 'bpmn:EndEvent',
eventDefinitionType: 'bpmn:MessageEventDefinition'
}
}
],
'bpmn:TimerEventDefinition': [
{
label: 'Timer start event',
actionName: 'replace-with-timer-start',
className: 'bpmn-icon-start-event-timer',
target: {
type: 'bpmn:StartEvent',
eventDefinitionType: 'bpmn:TimerEventDefinition',
isInterrupting: true
}
},
{
label: 'Timer intermediate catch event',
actionName: 'replace-with-timer-intermediate-catch',
className: 'bpmn-icon-intermediate-event-catch-timer',
target: {
type: 'bpmn:IntermediateCatchEvent',
eventDefinitionType: 'bpmn:TimerEventDefinition'
}
}
],
'bpmn:ConditionalEventDefinition': [
{
label: 'Conditional start event',
actionName: 'replace-with-conditional-start',
className: 'bpmn-icon-start-event-condition',
target: {
type: 'bpmn:StartEvent',
eventDefinitionType: 'bpmn:ConditionalEventDefinition',
isInterrupting: true
}
},
{
label: 'Conditional intermediate catch event',
actionName: 'replace-with-conditional-intermediate-catch',
className: 'bpmn-icon-intermediate-event-catch-condition',
target: {
type: 'bpmn:IntermediateCatchEvent',
eventDefinitionType: 'bpmn:ConditionalEventDefinition'
}
}
],
'bpmn:SignalEventDefinition': [
{
label: 'Signal start event',
actionName: 'replace-with-signal-start',
className: 'bpmn-icon-start-event-signal',
target: {
type: 'bpmn:StartEvent',
eventDefinitionType: 'bpmn:SignalEventDefinition',
isInterrupting: true
}
},
{
label: 'Signal intermediate catch event',
actionName: 'replace-with-signal-intermediate-catch',
className: 'bpmn-icon-intermediate-event-catch-signal',
target: {
type: 'bpmn:IntermediateCatchEvent',
eventDefinitionType: 'bpmn:SignalEventDefinition'
}
},
{
label: 'Signal intermediate throw event',
actionName: 'replace-with-signal-intermediate-throw',
className: 'bpmn-icon-intermediate-event-throw-signal',
target: {
type: 'bpmn:IntermediateThrowEvent',
eventDefinitionType: 'bpmn:SignalEventDefinition'
}
},
{
label: 'Signal end event',
actionName: 'replace-with-signal-end',
className: 'bpmn-icon-end-event-signal',
target: {
type: 'bpmn:EndEvent',
eventDefinitionType: 'bpmn:SignalEventDefinition'
}
}
],
'bpmn:ErrorEventDefinition': [
{
label: 'Error start event',
actionName: 'replace-with-error-start',
className: 'bpmn-icon-start-event-error',
target: {
type: 'bpmn:StartEvent',
eventDefinitionType: 'bpmn:ErrorEventDefinition',
isInterrupting: true
}
},
{
label: 'Error end event',
actionName: 'replace-with-error-end',
className: 'bpmn-icon-end-event-error',
target: {
type: 'bpmn:EndEvent',
eventDefinitionType: 'bpmn:ErrorEventDefinition'
}
}
],
'bpmn:EscalationEventDefinition': [
{
label: 'Escalation start event',
actionName: 'replace-with-escalation-start',
className: 'bpmn-icon-start-event-escalation',
target: {
type: 'bpmn:StartEvent',
eventDefinitionType: 'bpmn:EscalationEventDefinition',
isInterrupting: true
}
},
{
label: 'Escalation intermediate throw event',
actionName: 'replace-with-escalation-intermediate-throw',
className: 'bpmn-icon-intermediate-event-throw-escalation',
target: {
type: 'bpmn:IntermediateThrowEvent',
eventDefinitionType: 'bpmn:EscalationEventDefinition'
}
},
{
label: 'Escalation end event',
actionName: 'replace-with-escalation-end',
className: 'bpmn-icon-end-event-escalation',
target: {
type: 'bpmn:EndEvent',
eventDefinitionType: 'bpmn:EscalationEventDefinition'
}
}
],
'bpmn:CompensateEventDefinition': [
{
label: 'Compensation start event',
actionName: 'replace-with-compensation-start',
className: 'bpmn-icon-start-event-compensation',
target: {
type: 'bpmn:StartEvent',
eventDefinitionType: 'bpmn:CompensateEventDefinition',
isInterrupting: true
}
},
{
label: 'Compensation intermediate throw event',
actionName: 'replace-with-compensation-intermediate-throw',
className: 'bpmn-icon-intermediate-event-throw-compensation',
target: {
type: 'bpmn:IntermediateThrowEvent',
eventDefinitionType: 'bpmn:CompensateEventDefinition'
}
},
{
label: 'Compensation end event',
actionName: 'replace-with-compensation-end',
className: 'bpmn-icon-end-event-compensation',
target: {
type: 'bpmn:EndEvent',
eventDefinitionType: 'bpmn:CompensateEventDefinition'
}
}
]
};
================================================
FILE: lib/features/replace/index.js
================================================
import CopyPasteModule from '../copy-paste';
import ReplaceModule from 'diagram-js/lib/features/replace';
import SelectionModule from 'diagram-js/lib/features/selection';
import BpmnReplace from './BpmnReplace';
export default {
__depends__: [
CopyPasteModule,
ReplaceModule,
SelectionModule
],
bpmnReplace: [ 'type', BpmnReplace ]
};
================================================
FILE: lib/features/replace-preview/BpmnReplacePreview.js
================================================
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import inherits from 'inherits-browser';
import { escapeCSS as cssEscape } from 'diagram-js/lib/util/EscapeUtil';
import {
assign,
forEach
} from 'min-dash';
import {
query as domQuery
} from 'min-dom';
import {
attr as svgAttr
} from 'tiny-svg';
/**
* @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
* @typedef {import('diagram-js/lib/core/ElementRegistry').default} ElementRegistry
* @typedef {import('diagram-js/lib/core/ElementFactory').default} ElementFactory
* @typedef {import('diagram-js/lib/core/Canvas').default} Canvas
* @typedef {import('diagram-js/lib/features/preview-support/PreviewSupport').default} PreviewSupport
*/
var LOW_PRIORITY = 250;
/**
* @param {EventBus} eventBus
* @param {ElementRegistry} elementRegistry
* @param {ElementFactory} elementFactory
* @param {Canvas} canvas
* @param {PreviewSupport} previewSupport
*/
export default function BpmnReplacePreview(
eventBus, elementRegistry, elementFactory,
canvas, previewSupport) {
CommandInterceptor.call(this, eventBus);
/**
* Replace the visuals of all elements in the context which can be replaced
*
* @param {Object} context
*/
function replaceVisual(context) {
var replacements = context.canExecute.replacements;
forEach(replacements, function(replacement) {
var id = replacement.oldElementId;
var newElement = {
type: replacement.newElementType
};
// if the visual of the element is already replaced
if (context.visualReplacements[id]) {
return;
}
var element = elementRegistry.get(id);
assign(newElement, { x: element.x, y: element.y });
// create a temporary shape
var tempShape = elementFactory.createShape(newElement);
canvas.addShape(tempShape, element.parent);
// select the original SVG element related to the element and hide it
var gfx = domQuery('[data-element-id="' + cssEscape(element.id) + '"]', context.dragGroup);
if (gfx) {
svgAttr(gfx, { display: 'none' });
}
// clone the gfx of the temporary shape and add it to the drag group
var dragger = previewSupport.addDragger(tempShape, context.dragGroup);
context.visualReplacements[id] = dragger;
canvas.removeShape(tempShape);
});
}
/**
* Restore the original visuals of the previously replaced elements
*
* @param {Object} context
*/
function restoreVisual(context) {
var visualReplacements = context.visualReplacements;
forEach(visualReplacements, function(dragger, id) {
var originalGfx = domQuery('[data-element-id="' + cssEscape(id) + '"]', context.dragGroup);
if (originalGfx) {
svgAttr(originalGfx, { display: 'inline' });
}
dragger.remove();
if (visualReplacements[id]) {
delete visualReplacements[id];
}
});
}
eventBus.on('shape.move.move', LOW_PRIORITY, function(event) {
var context = event.context,
canExecute = context.canExecute;
if (!context.visualReplacements) {
context.visualReplacements = {};
}
if (canExecute && canExecute.replacements) {
replaceVisual(context);
} else {
restoreVisual(context);
}
});
}
BpmnReplacePreview.$inject = [
'eventBus',
'elementRegistry',
'elementFactory',
'canvas',
'previewSupport'
];
inherits(BpmnReplacePreview, CommandInterceptor);
================================================
FILE: lib/features/replace-preview/index.js
================================================
import PreviewSupportModule from 'diagram-js/lib/features/preview-support';
import BpmnReplacePreview from './BpmnReplacePreview';
export default {
__depends__: [
PreviewSupportModule
],
__init__: [ 'bpmnReplacePreview' ],
bpmnReplacePreview: [ 'type', BpmnReplacePreview ]
};
================================================
FILE: lib/features/rules/BpmnRules.js
================================================
import {
every,
find,
forEach,
some
} from 'min-dash';
import inherits from 'inherits-browser';
import {
is,
getBusinessObject
} from '../../util/ModelUtil';
import {
getParent,
isAny
} from '../modeling/util/ModelingUtil';
import {
isLabel
} from '../../util/LabelUtil';
import {
isExpanded,
isEventSubProcess,
isInterrupting,
hasErrorEventDefinition,
hasEscalationEventDefinition,
hasCompensateEventDefinition
} from '../../util/DiUtil';
import RuleProvider from 'diagram-js/lib/features/rules/RuleProvider';
import {
getBoundaryAttachment as isBoundaryAttachment
} from '../snapping/BpmnSnappingUtil';
import { isConnection } from 'diagram-js/lib/util/ModelUtil';
/**
* @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
*
* @typedef {import('../../model/Types').Connection} Connection
* @typedef {import('../../model/Types').Element} Element
* @typedef {import('../../model/Types').Shape} Shape
* @typedef {import('../../model/Types').ModdleElement} ModdleElement
*
* @typedef {import('diagram-js/lib/util/Types').Point} Point
* @typedef {import('diagram-js/lib/util/Types').Rect} Rect
*
* @typedef { {
* associationDirection?: 'None' | 'One' | 'Both';
* type: string;
* } | boolean | null } CanConnectResult
*
* @typedef { {
* id: string;
* type: string;
* } | boolean } CanReplaceResult
*/
/**
* BPMN-specific modeling rules.
*
* @param {EventBus} eventBus
*/
export default function BpmnRules(eventBus) {
RuleProvider.call(this, eventBus);
}
inherits(BpmnRules, RuleProvider);
BpmnRules.$inject = [ 'eventBus' ];
BpmnRules.prototype.init = function() {
this.addRule('connection.start', function(context) {
var source = context.source;
return canStartConnection(source);
});
this.addRule('connection.create', function(context) {
var source = context.source,
target = context.target,
hints = context.hints || {},
targetParent = hints.targetParent,
targetAttach = hints.targetAttach;
// don't allow incoming connections on
// newly created boundary events
// to boundary events
if (targetAttach) {
return false;
}
// temporarily set target parent for scoping
// checks to work
if (targetParent) {
target.parent = targetParent;
}
try {
return canConnect(source, target);
} finally {
// unset temporary target parent
if (targetParent) {
target.parent = null;
}
}
});
this.addRule('connection.reconnect', function(context) {
var connection = context.connection,
source = context.source,
target = context.target;
return canConnect(source, target, connection);
});
this.addRule('connection.updateWaypoints', function(context) {
return {
type: context.connection.type
};
});
this.addRule('shape.resize', function(context) {
var shape = context.shape,
newBounds = context.newBounds;
return canResize(shape, newBounds);
});
this.addRule('elements.create', function(context) {
var elements = context.elements,
position = context.position,
target = context.target;
if (isConnection(target) && !canInsert(elements, target, position)) {
return false;
}
return every(elements, function(element) {
if (isConnection(element)) {
return canConnect(element.source, element.target, element);
}
if (element.host) {
return canAttach(element, element.host, null, position);
}
return canCreate(element, target, null, position);
});
});
this.addRule('elements.move', function(context) {
var target = context.target,
shapes = context.shapes,
position = context.position,
hints = context.hints;
// prevent keyboard movement of boundary events without host
if (hints?.keyboardMove) {
const hasAttachedEventWithoutHost = shapes.some(function(shape) {
return isBoundaryEvent(shape) && !shapes.includes(shape.host);
});
if (hasAttachedEventWithoutHost) {
return false;
}
}
return canAttach(shapes, target, null, position) ||
canReplace(shapes, target, position) ||
canMove(shapes, target, position) ||
canInsert(shapes, target, position);
});
this.addRule('shape.create', function(context) {
return canCreate(
context.shape,
context.target,
context.source,
context.position
);
});
this.addRule('shape.attach', function(context) {
return canAttach(
context.shape,
context.target,
null,
context.position
);
});
this.addRule('element.copy', function(context) {
var element = context.element,
elements = context.elements;
return canCopy(elements, element);
});
};
BpmnRules.prototype.canConnectMessageFlow = canConnectMessageFlow;
BpmnRules.prototype.canConnectSequenceFlow = canConnectSequenceFlow;
BpmnRules.prototype.canConnectDataAssociation = canConnectDataAssociation;
BpmnRules.prototype.canConnectAssociation = canConnectAssociation;
BpmnRules.prototype.canConnectCompensationAssociation = canConnectCompensationAssociation;
BpmnRules.prototype.canMove = canMove;
BpmnRules.prototype.canAttach = canAttach;
BpmnRules.prototype.canReplace = canReplace;
BpmnRules.prototype.canDrop = canDrop;
BpmnRules.prototype.canInsert = canInsert;
BpmnRules.prototype.canCreate = canCreate;
BpmnRules.prototype.canConnect = canConnect;
BpmnRules.prototype.canResize = canResize;
BpmnRules.prototype.canCopy = canCopy;
/**
* Utility functions for rule checking
*/
/**
* Checks if given element can be used for starting connection.
*
* @param {Element} source
*
* @return {boolean}
*/
function canStartConnection(element) {
if (nonExistingOrLabel(element)) {
return null;
}
return isAny(element, [
'bpmn:FlowNode',
'bpmn:InteractionNode',
'bpmn:DataObjectReference',
'bpmn:DataStoreReference',
'bpmn:Group',
'bpmn:TextAnnotation'
]);
}
/**
* @param {Element} element
*
* @return {boolean}
*/
function nonExistingOrLabel(element) {
return !element || isLabel(element);
}
/**
* @param {Element} element
*
* @return {ModdleElement}
*/
function getOrganizationalParent(element) {
do {
if (is(element, 'bpmn:Process')) {
return getBusinessObject(element);
}
if (is(element, 'bpmn:Participant')) {
return (
getBusinessObject(element).processRef ||
getBusinessObject(element)
);
}
} while ((element = element.parent));
}
/**
* @param {Element} element
*
* @return {boolean}
*/
function isTextAnnotation(element) {
return is(element, 'bpmn:TextAnnotation');
}
/**
* @param {Element} element
*
* @return {boolean}
*/
function isGroup(element) {
return is(element, 'bpmn:Group') && !element.labelTarget;
}
/**
* @param {Element} element
*
* @return {boolean}
*/
function isCompensationBoundary(element) {
return is(element, 'bpmn:BoundaryEvent') &&
hasEventDefinition(element, 'bpmn:CompensateEventDefinition');
}
/**
* @param {Element} element
*
* @return {boolean}
*/
function isForCompensation(element) {
return getBusinessObject(element).isForCompensation;
}
/**
* @param {Element} a
* @param {Element} b
*
* @return {boolean}
*/
function isSameOrganization(a, b) {
var parentA = getOrganizationalParent(a),
parentB = getOrganizationalParent(b);
return parentA === parentB;
}
/**
* @param {Element} element
*
* @return {boolean}
*/
function isMessageFlowSource(element) {
return (
is(element, 'bpmn:InteractionNode') &&
!is(element, 'bpmn:BoundaryEvent') && (
!is(element, 'bpmn:Event') || (
is(element, 'bpmn:ThrowEvent') &&
hasEventDefinitionOrNone(element, 'bpmn:MessageEventDefinition')
)
)
);
}
/**
* @param {Element} element
*
* @return {boolean}
*/
function isMessageFlowTarget(element) {
return (
is(element, 'bpmn:InteractionNode') &&
!isForCompensation(element) && (
!is(element, 'bpmn:Event') || (
is(element, 'bpmn:CatchEvent') &&
hasEventDefinitionOrNone(element, 'bpmn:MessageEventDefinition')
)
) && !(
is(element, 'bpmn:BoundaryEvent') &&
!hasEventDefinition(element, 'bpmn:MessageEventDefinition')
)
);
}
/**
* @param {Element} element
*
* @return {ModdleElement}
*/
function getScopeParent(element) {
var parent = element;
while ((parent = parent.parent)) {
if (is(parent, 'bpmn:FlowElementsContainer')) {
return getBusinessObject(parent);
}
if (is(parent, 'bpmn:Participant')) {
return getBusinessObject(parent).processRef;
}
}
return null;
}
/**
* @param {Element} a
* @param {Element} b
*
* @return {boolean}
*/
function isSameScope(a, b) {
var scopeParentA = getScopeParent(a),
scopeParentB = getScopeParent(b);
return scopeParentA === scopeParentB;
}
/**
* @param {Element} element
* @param {string} eventDefinition
*
* @return {boolean}
*/
function hasEventDefinition(element, eventDefinition) {
var businessObject = getBusinessObject(element);
return !!find(businessObject.eventDefinitions || [], function(definition) {
return is(definition, eventDefinition);
});
}
/**
* @param {Element} element
* @param {string} eventDefinition
*
* @return {boolean}
*/
function hasEventDefinitionOrNone(element, eventDefinition) {
var businessObject = getBusinessObject(element);
return (businessObject.eventDefinitions || []).every(function(definition) {
return is(definition, eventDefinition);
});
}
/**
* @param {Element} element
*
* @return {boolean}
*/
function isSequenceFlowSource(element) {
return (
is(element, 'bpmn:FlowNode') &&
!is(element, 'bpmn:EndEvent') &&
!isEventSubProcess(element) &&
!(is(element, 'bpmn:IntermediateThrowEvent') &&
hasEventDefinition(element, 'bpmn:LinkEventDefinition')
) &&
!isCompensationBoundary(element) &&
!isForCompensation(element)
);
}
/**
* @param {Element} element
*
* @return {boolean}
*/
function isSequenceFlowTarget(element) {
return (
is(element, 'bpmn:FlowNode') &&
!is(element, 'bpmn:StartEvent') &&
!is(element, 'bpmn:BoundaryEvent') &&
!isEventSubProcess(element) &&
!(is(element, 'bpmn:IntermediateCatchEvent') &&
hasEventDefinition(element, 'bpmn:LinkEventDefinition')
) &&
!isForCompensation(element)
);
}
/**
* @param {Element} element
*
* @return {boolean}
*/
function isEventBasedTarget(element) {
return (
is(element, 'bpmn:ReceiveTask') || (
is(element, 'bpmn:IntermediateCatchEvent') && (
hasEventDefinition(element, 'bpmn:MessageEventDefinition') ||
hasEventDefinition(element, 'bpmn:TimerEventDefinition') ||
hasEventDefinition(element, 'bpmn:ConditionalEventDefinition') ||
hasEventDefinition(element, 'bpmn:SignalEventDefinition')
)
)
);
}
/**
* @param {Element} element
*
* @return {Shape[]}
*/
function getParents(element) {
var parents = [];
while (element) {
element = element.parent;
if (element) {
parents.push(element);
}
}
return parents;
}
/**
* @param {Shape} possibleParent
* @param {Element} element
*
* @return {boolean}
*/
function isParent(possibleParent, element) {
var allParents = getParents(element);
return allParents.indexOf(possibleParent) !== -1;
}
/**
* @param {Element} source
* @param {Element} target
* @param {Connection} connection
*
* @return {CanConnectResult}
*/
function canConnect(source, target, connection) {
if (nonExistingOrLabel(source) || nonExistingOrLabel(target)) {
return null;
}
if (!is(connection, 'bpmn:DataAssociation')) {
if (canConnectMessageFlow(source, target)) {
return { type: 'bpmn:MessageFlow' };
}
if (canConnectSequenceFlow(source, target)) {
return { type: 'bpmn:SequenceFlow' };
}
}
var connectDataAssociation = canConnectDataAssociation(source, target);
if (connectDataAssociation) {
return connectDataAssociation;
}
if (canConnectCompensationAssociation(source, target)) {
return {
type: 'bpmn:Association',
associationDirection: 'One'
};
}
if (canConnectAssociation(source, target)) {
return {
type: 'bpmn:Association',
associationDirection: 'None'
};
}
return false;
}
/**
* Can an element be dropped into the target element.
*
* @param {Element} element
* @param {Shape} target
*
* @return {boolean}
*/
function canDrop(element, target) {
// can move labels and groups everywhere
if (isLabel(element) || isGroup(element)) {
return true;
}
// disallow to create elements on collapsed pools
if (is(target, 'bpmn:Participant') && !isExpanded(target)) {
return false;
}
// allow to create new participants on
// existing collaboration and process diagrams
if (is(element, 'bpmn:Participant')) {
return is(target, 'bpmn:Process') || is(target, 'bpmn:Collaboration');
}
// allow moving DataInput / DataOutput within its original container only
if (isAny(element, [ 'bpmn:DataInput', 'bpmn:DataOutput' ])) {
if (element.parent) {
return target === element.parent;
}
}
// allow creating lanes on participants and other lanes only
if (is(element, 'bpmn:Lane')) {
return is(target, 'bpmn:Participant') || is(target, 'bpmn:Lane');
}
// disallow dropping boundary events which cannot replace with intermediate event
if (is(element, 'bpmn:BoundaryEvent') && !isDroppableBoundaryEvent(element)) {
return false;
}
// drop flow elements onto flow element containers
// and participants
if (is(element, 'bpmn:FlowElement') && !is(element, 'bpmn:DataStoreReference')) {
if (is(target, 'bpmn:FlowElementsContainer')) {
return isExpanded(target);
}
return isAny(target, [ 'bpmn:Participant', 'bpmn:Lane' ]);
}
// disallow dropping data store reference if there is no process to append to
if (is(element, 'bpmn:DataStoreReference') && is(target, 'bpmn:Collaboration')) {
return some(getBusinessObject(target).get('participants'), function(participant) {
return !!participant.get('processRef');
});
}
// account for the fact that data associations are always
// rendered and moved to top (Process or Collaboration level)
//
// artifacts may be placed wherever, too
if (isAny(element, [ 'bpmn:Artifact', 'bpmn:DataAssociation', 'bpmn:DataStoreReference' ])) {
return isAny(target, [
'bpmn:Collaboration',
'bpmn:Lane',
'bpmn:Participant',
'bpmn:Process',
'bpmn:SubProcess' ]);
}
if (is(element, 'bpmn:MessageFlow')) {
return is(target, 'bpmn:Collaboration')
|| element.source.parent == target
|| element.target.parent == target;
}
return false;
}
/**
* @param {Shape} event
*
* @return {boolean}
*/
function isDroppableBoundaryEvent(event) {
return getBusinessObject(event).cancelActivity && (
hasNoEventDefinition(event) || hasCommonBoundaryIntermediateEventDefinition(event)
);
}
/**
* @param {Element} element
*
* @return {boolean}
*/
function isBoundaryEvent(element) {
return !isLabel(element) && is(element, 'bpmn:BoundaryEvent');
}
/**
* @param {Element} element
*
* @return {boolean}
*/
function isLane(element) {
return is(element, 'bpmn:Lane');
}
/**
* `bpmn:IntermediateThrowEvents` are treated as boundary events during create.
*
* @param {Element} element
*
* @return {boolean}
*/
function isBoundaryCandidate(element) {
if (isBoundaryEvent(element)) {
return true;
}
if (is(element, 'bpmn:IntermediateThrowEvent') && hasNoEventDefinition(element)) {
return true;
}
return (
is(element, 'bpmn:IntermediateCatchEvent') &&
hasCommonBoundaryIntermediateEventDefinition(element)
);
}
/**
* @param {Element} element
*
* @return {boolean}
*/
function hasNoEventDefinition(element) {
var businessObject = getBusinessObject(element);
return businessObject && !(businessObject.eventDefinitions && businessObject.eventDefinitions.length);
}
/**
* @param {Element} element
*
* @return {boolean}
*/
function hasCommonBoundaryIntermediateEventDefinition(element) {
return hasOneOfEventDefinitions(element, [
'bpmn:MessageEventDefinition',
'bpmn:TimerEventDefinition',
'bpmn:SignalEventDefinition',
'bpmn:ConditionalEventDefinition'
]);
}
/**
* @param {Element} element
* @param {string[]} eventDefinitions
*
* @return {boolean}
*/
function hasOneOfEventDefinitions(element, eventDefinitions) {
return eventDefinitions.some(function(definition) {
return hasEventDefinition(element, definition);
});
}
/**
* @param {Element} element
*
* @return {boolean}
*/
function isReceiveTaskAfterEventBasedGateway(element) {
return (
is(element, 'bpmn:ReceiveTask') &&
find(element.incoming, function(incoming) {
return is(incoming.source, 'bpmn:EventBasedGateway');
})
);
}
/**
* TODO(philippfromme): remove `source` parameter
*
* @param {Element[]} elements
* @param {Shape} target
* @param {Element} source
* @param {Point} [position]
*
* @return {boolean | 'attach'}
*/
function canAttach(elements, target, source, position) {
if (!Array.isArray(elements)) {
elements = [ elements ];
}
// only (re-)attach one element at a time
if (elements.length !== 1) {
return false;
}
var element = elements[0];
// do not attach labels
if (isLabel(element)) {
return false;
}
// only handle boundary events
if (!isBoundaryCandidate(element)) {
return false;
}
// disallow drop on event sub processes
if (isEventSubProcess(target)) {
return false;
}
// only allow drop on non compensation activities
if (!is(target, 'bpmn:Activity') || isForCompensation(target)) {
return false;
}
// only attach to subprocess border
if (position && !isBoundaryAttachment(position, target)) {
return false;
}
// do not attach on receive tasks after event based gateways
if (isReceiveTaskAfterEventBasedGateway(target)) {
return false;
}
return 'attach';
}
/**
* Check whether the given elements can be replaced. Return all elements which
* can be replaced.
*
* @example
*
* ```javascript
* [{
* id: 'IntermediateEvent_1',
* type: 'bpmn:StartEvent'
* },
* {
* id: 'Task_1',
* type: 'bpmn:ServiceTask'
* }]
* ```
*
* @param {Element[]} elements
* @param {Shape} [target]
* @param {Point} [position]
*
* @return {CanReplaceResult}
*/
function canReplace(elements, target, position) {
if (!target) {
return false;
}
var canExecute = {
replacements: []
};
forEach(elements, function(element) {
if (!isEventSubProcess(target)) {
if (is(element, 'bpmn:StartEvent') &&
element.type !== 'label' &&
canDrop(element, target)) {
// replace a non-interrupting start event by a blank interrupting start event
// when the target is not an event sub process
if (!isInterrupting(element)) {
canExecute.replacements.push({
oldElementId: element.id,
newElementType: 'bpmn:StartEvent'
});
}
// replace an error/escalation/compensate start event by a blank interrupting start event
// when the target is not an event sub process
if (hasErrorEventDefinition(element) ||
hasEscalationEventDefinition(element) ||
hasCompensateEventDefinition(element)) {
canExecute.replacements.push({
oldElementId: element.id,
newElementType: 'bpmn:StartEvent'
});
}
// replace a typed start event by a blank interrupting start event
// when the target is a sub process but not an event sub process
if (hasOneOfEventDefinitions(element,
[
'bpmn:MessageEventDefinition',
'bpmn:TimerEventDefinition',
'bpmn:SignalEventDefinition',
'bpmn:ConditionalEventDefinition'
]) &&
is(target, 'bpmn:SubProcess')) {
canExecute.replacements.push({
oldElementId: element.id,
newElementType: 'bpmn:StartEvent'
});
}
}
}
if (!is(target, 'bpmn:Transaction')) {
if (hasEventDefinition(element, 'bpmn:CancelEventDefinition') &&
element.type !== 'label') {
if (is(element, 'bpmn:EndEvent') && canDrop(element, target)) {
canExecute.replacements.push({
oldElementId: element.id,
newElementType: 'bpmn:EndEvent'
});
}
if (is(element, 'bpmn:BoundaryEvent') && canAttach(element, target, null, position)) {
canExecute.replacements.push({
oldElementId: element.id,
newElementType: 'bpmn:BoundaryEvent'
});
}
}
}
});
return canExecute.replacements.length ? canExecute : false;
}
/**
* @param {Element[]} elements
* @param {Shape} target
*
* @return {boolean}
*/
function canMove(elements, target) {
// do not move selection containing lanes
if (some(elements, isLane)) {
return false;
}
// allow default move check to start move operation
if (!target) {
return true;
}
return elements.every(function(element) {
return canDrop(element, target);
});
}
/**
* @param {Shape} shape
* @param {Shape} target
* @param {Element} source
* @param {Point} position
*
* @return {boolean}
*/
function canCreate(shape, target, source, position) {
if (!target) {
return false;
}
if (isLabel(shape) || isGroup(shape)) {
return true;
}
return canDrop(shape, target, position) || canInsert(shape, target, position);
}
/**
* @param {Shape} shape
* @param {Rect} newBounds
*
* @return {boolean}
*/
function canResize(shape, newBounds) {
if (is(shape, 'bpmn:SubProcess')) {
return (
isExpanded(shape) && (
!newBounds || (newBounds.width >= 100 && newBounds.height >= 80)
)
);
}
if (is(shape, 'bpmn:Lane')) {
return true;
}
if (is(shape, 'bpmn:Participant')) {
return true;
}
if (isTextAnnotation(shape)) {
return true;
}
if (isGroup(shape)) {
return true;
}
return false;
}
/**
* Check whether one of of the elements to be connected is a text annotation.
*
* @param {Element} source
* @param {Element} target
*
* @return {boolean}
*/
function isOneTextAnnotation(source, target) {
var sourceTextAnnotation = isTextAnnotation(source),
targetTextAnnotation = isTextAnnotation(target);
return (
(sourceTextAnnotation || targetTextAnnotation) &&
(sourceTextAnnotation !== targetTextAnnotation)
);
}
/**
* @param {Element} source
* @param {Element} target
*
* @return {CanConnectResult}
*/
function canConnectAssociation(source, target) {
// don't connect parent <-> child
if (isParent(target, source) || isParent(source, target)) {
return false;
}
// allow connection of associations between and
if (isOneTextAnnotation(source, target)) {
return true;
}
// can connect associations where we can connect
// data associations, too (!)
return !!canConnectDataAssociation(source, target);
}
/**
* @param {Element} source
* @param {Element} target
*
* @return {boolean}
*/
function canConnectCompensationAssociation(source, target) {
return (
isSameScope(source, target) &&
isCompensationBoundary(source) &&
is(target, 'bpmn:Activity') &&
!isHostOfElement(target, source) &&
!isEventSubProcess(target)
);
}
/**
* @param {Element} source
* @param {Element} target
*
* @return {boolean}
*/
function canConnectMessageFlow(source, target) {
// during connect user might move mouse out of canvas
// https://github.com/bpmn-io/bpmn-js/issues/1033
if (getRootElement(source) && !getRootElement(target)) {
return false;
}
return (
isMessageFlowSource(source) &&
isMessageFlowTarget(target) &&
!isSameOrganization(source, target)
);
}
/**
* @param {Element} source
* @param {Element} target
*
* @return {boolean}
*/
function canConnectSequenceFlow(source, target) {
return isSequenceFlowSource(source) &&
isSequenceFlowTarget(target) &&
isSameScope(source, target) &&
!(is(source, 'bpmn:EventBasedGateway') && !isEventBasedTarget(target));
}
/**
* @param {Element} source
* @param {Element} target
*
* @return {CanConnectResult}
*/
function canConnectDataAssociation(source, target) {
if (isAny(source, [ 'bpmn:DataObjectReference', 'bpmn:DataStoreReference' ]) &&
isAny(target, [ 'bpmn:Activity', 'bpmn:ThrowEvent' ])) {
return { type: 'bpmn:DataInputAssociation' };
}
if (isAny(target, [ 'bpmn:DataObjectReference', 'bpmn:DataStoreReference' ]) &&
isAny(source, [ 'bpmn:Activity', 'bpmn:CatchEvent' ])) {
return { type: 'bpmn:DataOutputAssociation' };
}
return false;
}
/**
* @param {Shape} shape
* @param {Connection} connection
* @param {Point} position
*
* @return {boolean}
*/
function canInsert(shape, connection, position) {
if (!connection) {
return false;
}
if (Array.isArray(shape)) {
if (shape.length !== 1) {
return false;
}
shape = shape[ 0 ];
}
if (connection.source === shape ||
connection.target === shape) {
return false;
}
// return true if shape can be inserted into connection parent
return (
isAny(connection, [ 'bpmn:SequenceFlow', 'bpmn:MessageFlow' ]) &&
!isLabel(connection) &&
is(shape, 'bpmn:FlowNode') &&
!is(shape, 'bpmn:BoundaryEvent') &&
canDrop(shape, connection.parent, position));
}
/**
* @param {Element[]} elements
* @param {Element} element
*
* @return {boolean}
*/
function includes(elements, element) {
return (elements && element) && elements.indexOf(element) !== -1;
}
/**
* @param {Element[]} elements
* @param {Element} element
*
* @return {boolean}
*/
function canCopy(elements, element) {
if (isLabel(element)) {
return true;
}
if (is(element, 'bpmn:Lane') && !includes(elements, element.parent)) {
return false;
}
return true;
}
/**
* @param {Element} element
*
* @return {Element|null}
*/
function getRootElement(element) {
return getParent(element, 'bpmn:Process') || getParent(element, 'bpmn:Collaboration');
}
function isHostOfElement(potentialHost, element) {
return potentialHost.attachers.includes(element);
}
================================================
FILE: lib/features/rules/index.js
================================================
import RulesModule from 'diagram-js/lib/features/rules';
import BpmnRules from './BpmnRules';
export default {
__depends__: [
RulesModule
],
__init__: [ 'bpmnRules' ],
bpmnRules: [ 'type', BpmnRules ]
};
================================================
FILE: lib/features/search/BpmnSearchProvider.js
================================================
import {
getLabel,
isLabel
} from '../../util/LabelUtil';
/**
* @typedef {import('diagram-js/lib/core/Canvas').default} Canvas
* @typedef {import('diagram-js/lib/core/ElementRegistry').default} ElementRegistry
* @typedef {import('diagram-js/lib/features/search-pad/SearchPad').default} SearchPad
*
* @typedef {import('diagram-js/lib/features/search-pad/SearchPadProvider').default} SearchPadProvider
* @typedef {import('diagram-js/lib/features/search-pad/SearchPadProvider').SearchResult} SearchPadResult
* @typedef {import('diagram-js/lib/features/search-pad/SearchPadProvider').Token} SearchPadToken
* @typedef {import('diagram-js/lib/features/search/search').default} Search
* @typedef {import('diagram-js/lib/features/search/search').SearchResult} SearchResult
* @typedef {import('diagram-js/lib/features/search/search').Token} SearchToken
*/
/**
* Provides ability to search for BPMN elements.
*
* @implements {SearchPadProvider}
*
* @param {ElementRegistry} elementRegistry
* @param {SearchPad} searchPad
* @param {Canvas} canvas
* @param {Search} search
*/
export default function BpmnSearchProvider(elementRegistry, searchPad, canvas, search) {
this._elementRegistry = elementRegistry;
this._canvas = canvas;
this._search = search;
searchPad.registerProvider(this);
}
BpmnSearchProvider.$inject = [
'elementRegistry',
'searchPad',
'canvas',
'search'
];
/**
* @param {string} pattern
*
* @return {SearchPadResult[]}
*/
BpmnSearchProvider.prototype.find = function(pattern) {
var rootElements = this._canvas.getRootElements();
var elements = this._elementRegistry.filter(function(element) {
return !isLabel(element) && !rootElements.includes(element);
});
return this._search(
elements.map(element => {
return {
element,
label: getLabel(element),
id: element.id
};
}),
pattern, {
keys: [
'label',
'id'
]
}
).map(toSearchPadResult);
};
/**
* @param {SearchResult} token
*
* @return {SearchPadResult}
*/
function toSearchPadResult(result) {
const {
item: {
element
},
tokens
} = result;
return {
element,
primaryTokens: tokens.label,
secondaryTokens: tokens.id
};
}
================================================
FILE: lib/features/search/index.js
================================================
import SearchPadModule from 'diagram-js/lib/features/search-pad';
import SearchModule from 'diagram-js/lib/features/search';
import BpmnSearchProvider from './BpmnSearchProvider';
export default {
__depends__: [
SearchPadModule,
SearchModule
],
__init__: [ 'bpmnSearch' ],
bpmnSearch: [ 'type', BpmnSearchProvider ]
};
================================================
FILE: lib/features/snapping/BpmnConnectSnapping.js
================================================
import {
mid,
setSnapped
} from 'diagram-js/lib/features/snapping/SnapUtil';
import { isCmd } from 'diagram-js/lib/features/keyboard/KeyboardUtil';
import {
getOrientation
} from 'diagram-js/lib/layout/LayoutUtil';
import { is } from '../../util/ModelUtil';
import { isAny } from '../modeling/util/ModelingUtil';
import { some } from 'min-dash';
/**
* @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
*
* @typedef {import('diagram-js/lib/core/EventBus').Event} Event
*
* @typedef {import('../../model/Types').Shape} Shape
*
* @typedef {import('diagram-js/lib/util/Types').Axis} Axis
* @typedef {import('diagram-js/lib/util/Types').Point} Point
*/
var HIGHER_PRIORITY = 1250;
var BOUNDARY_TO_HOST_THRESHOLD = 40;
var TARGET_BOUNDS_PADDING = 20,
TASK_BOUNDS_PADDING = 10;
var TARGET_CENTER_PADDING = 20;
var AXES = [ 'x', 'y' ];
var abs = Math.abs;
/**
* Snap during connect.
*
* @param {EventBus} eventBus
*/
export default function BpmnConnectSnapping(eventBus) {
eventBus.on([
'connect.hover',
'connect.move',
'connect.end',
], HIGHER_PRIORITY, function(event) {
var context = event.context,
canExecute = context.canExecute,
start = context.start,
hover = context.hover,
source = context.source,
target = context.target;
// do NOT snap on CMD
if (event.originalEvent && isCmd(event.originalEvent)) {
return;
}
if (!context.initialConnectionStart) {
context.initialConnectionStart = context.connectionStart;
}
// snap hover
if (canExecute && hover) {
snapToShape(event, hover, getTargetBoundsPadding(hover));
}
if (hover && isAnyType(canExecute, [
'bpmn:Association',
'bpmn:DataInputAssociation',
'bpmn:DataOutputAssociation',
'bpmn:SequenceFlow'
])) {
context.connectionStart = mid(start);
// snap hover
if (isAny(hover, [ 'bpmn:Event', 'bpmn:Gateway' ])) {
snapToPosition(event, mid(hover));
}
// snap hover
if (isAny(hover, [ 'bpmn:Task', 'bpmn:SubProcess' ])) {
snapToTargetMid(event, hover);
}
// snap source and target
if (is(source, 'bpmn:BoundaryEvent') && target === source.host) {
snapBoundaryEventLoop(event);
}
} else if (isType(canExecute, 'bpmn:MessageFlow')) {
if (is(start, 'bpmn:Event')) {
// snap start
context.connectionStart = mid(start);
}
if (is(hover, 'bpmn:Event')) {
// snap hover
snapToPosition(event, mid(hover));
}
} else {
// un-snap source
context.connectionStart = context.initialConnectionStart;
}
});
}
BpmnConnectSnapping.$inject = [ 'eventBus' ];
// helpers //////////
/**
* Snap to the given target if the event is inside the bounds of the target.
*
* @param {Event} event
* @param {Shape} target
* @param {number} padding
*/
function snapToShape(event, target, padding) {
AXES.forEach(function(axis) {
var dimensionForAxis = getDimensionForAxis(axis, target);
if (event[ axis ] < target[ axis ] + padding) {
setSnapped(event, axis, target[ axis ] + padding);
} else if (event[ axis ] > target[ axis ] + dimensionForAxis - padding) {
setSnapped(event, axis, target[ axis ] + dimensionForAxis - padding);
}
});
}
/**
* Snap to the target mid if the event is in the target mid.
*
* @param {Event} event
* @param {Shape} target
*/
function snapToTargetMid(event, target) {
var targetMid = mid(target);
AXES.forEach(function(axis) {
if (isMid(event, target, axis)) {
setSnapped(event, axis, targetMid[ axis ]);
}
});
}
/**
* Snap to prevent a loop overlapping a boundary event.
*
* @param {Event} event
*/
function snapBoundaryEventLoop(event) {
var context = event.context,
source = context.source,
target = context.target;
if (isReverse(context)) {
return;
}
var sourceMid = mid(source),
orientation = getOrientation(sourceMid, target, -10),
axes = [];
if (/top|bottom/.test(orientation)) {
axes.push('x');
}
if (/left|right/.test(orientation)) {
axes.push('y');
}
axes.forEach(function(axis) {
var coordinate = event[ axis ], newCoordinate;
if (abs(coordinate - sourceMid[ axis ]) < BOUNDARY_TO_HOST_THRESHOLD) {
if (coordinate > sourceMid[ axis ]) {
newCoordinate = sourceMid[ axis ] + BOUNDARY_TO_HOST_THRESHOLD;
}
else {
newCoordinate = sourceMid[ axis ] - BOUNDARY_TO_HOST_THRESHOLD;
}
setSnapped(event, axis, newCoordinate);
}
});
}
/**
* @param {Event} event
* @param {Point} position
*/
function snapToPosition(event, position) {
setSnapped(event, 'x', position.x);
setSnapped(event, 'y', position.y);
}
function isType(attrs, type) {
return attrs && attrs.type === type;
}
function isAnyType(attrs, types) {
return some(types, function(type) {
return isType(attrs, type);
});
}
/**
* @param {Axis} axis
* @param {Shape} element
*
* @return {number}
*/
function getDimensionForAxis(axis, element) {
return axis === 'x' ? element.width : element.height;
}
/**
* @param {Shape} target
*
* @return {number}
*/
function getTargetBoundsPadding(target) {
if (is(target, 'bpmn:Task')) {
return TASK_BOUNDS_PADDING;
} else {
return TARGET_BOUNDS_PADDING;
}
}
/**
* @param {Event} event
* @param {Shape} target
* @param {Axis} axis
*
* @return {boolean}
*/
function isMid(event, target, axis) {
return event[ axis ] > target[ axis ] + TARGET_CENTER_PADDING
&& event[ axis ] < target[ axis ] + getDimensionForAxis(axis, target) - TARGET_CENTER_PADDING;
}
function isReverse(context) {
var hover = context.hover,
source = context.source;
return hover && source && hover === source;
}
================================================
FILE: lib/features/snapping/BpmnCreateMoveSnapping.js
================================================
import inherits from 'inherits-browser';
import CreateMoveSnapping from 'diagram-js/lib/features/snapping/CreateMoveSnapping';
import {
isSnapped,
setSnapped,
topLeft,
bottomRight
} from 'diagram-js/lib/features/snapping/SnapUtil';
import { isExpanded } from '../../util/DiUtil';
import { is } from '../../util/ModelUtil';
import {
asTRBL,
getMid
} from 'diagram-js/lib/layout/LayoutUtil';
import { getBoundaryAttachment } from './BpmnSnappingUtil';
import { forEach } from 'min-dash';
/**
* @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
* @typedef {import('didi').Injector} Injector
*
* @typedef {import('diagram-js/lib/features/snapping/SnapContext').default} SnapContext
* @typedef {import('diagram-js/lib/features/snapping/SnapContext').SnapPoints} SnapPoints
*
* @typedef {import('diagram-js/lib/core/EventBus').Event} Event
*
* @typedef {import('../../model/Types').Element} Element
* @typedef {import('../../model/Types').Shape} Shape
*/
var HIGH_PRIORITY = 1500;
/**
* Snap during create and move.
*
* @param {EventBus} eventBus
* @param {Injector} injector
*/
export default function BpmnCreateMoveSnapping(eventBus, injector) {
injector.invoke(CreateMoveSnapping, this);
// creating first participant
eventBus.on([ 'create.move', 'create.end' ], HIGH_PRIORITY, setSnappedIfConstrained);
// snap boundary events
eventBus.on([
'create.move',
'create.end',
'shape.move.move',
'shape.move.end'
], HIGH_PRIORITY, function(event) {
var context = event.context,
canExecute = context.canExecute,
target = context.target;
var canAttach = canExecute && (canExecute === 'attach' || canExecute.attach);
if (canAttach && !isSnapped(event)) {
snapBoundaryEvent(event, target);
}
});
}
inherits(BpmnCreateMoveSnapping, CreateMoveSnapping);
BpmnCreateMoveSnapping.$inject = [
'eventBus',
'injector'
];
/**
* @param {Event} event
*
* @return {SnapContext}
*/
BpmnCreateMoveSnapping.prototype.initSnap = function(event) {
var snapContext = CreateMoveSnapping.prototype.initSnap.call(this, event);
var shape = event.shape;
var isMove = !!this._elementRegistry.get(shape.id);
// snap to docking points
forEach(shape.outgoing, function(connection) {
var docking = connection.waypoints[0];
docking = docking.original || docking;
snapContext.setSnapOrigin(connection.id + '-docking', getDockingSnapOrigin(docking, isMove, event));
});
forEach(shape.incoming, function(connection) {
var docking = connection.waypoints[connection.waypoints.length - 1];
docking = docking.original || docking;
snapContext.setSnapOrigin(connection.id + '-docking', getDockingSnapOrigin(docking, isMove, event));
});
if (is(shape, 'bpmn:Participant')) {
// snap to borders with higher priority
snapContext.setSnapLocations([ 'top-left', 'bottom-right', 'mid' ]);
}
return snapContext;
};
/**
* @param {SnapPoints} snapPoints
* @param {Shape} shape
* @param {Shape} target
*
* @return {SnapPoints}
*/
BpmnCreateMoveSnapping.prototype.addSnapTargetPoints = function(snapPoints, shape, target) {
CreateMoveSnapping.prototype.addSnapTargetPoints.call(this, snapPoints, shape, target);
var snapTargets = this.getSnapTargets(shape, target);
forEach(snapTargets, function(snapTarget) {
// handle TRBL alignment
//
// * with container elements
// * with text annotations
if (isContainer(snapTarget) || areAll([ shape, snapTarget ], 'bpmn:TextAnnotation')) {
snapPoints.add('top-left', topLeft(snapTarget));
snapPoints.add('bottom-right', bottomRight(snapTarget));
}
});
var elementRegistry = this._elementRegistry;
// snap to docking points if not create mode
forEach(shape.incoming, function(connection) {
if (elementRegistry.get(shape.id)) {
if (!includes(snapTargets, connection.source)) {
snapPoints.add('mid', getMid(connection.source));
}
var docking = connection.waypoints[0];
snapPoints.add(connection.id + '-docking', docking.original || docking);
}
});
forEach(shape.outgoing, function(connection) {
if (elementRegistry.get(shape.id)) {
if (!includes(snapTargets, connection.target)) {
snapPoints.add('mid', getMid(connection.target));
}
var docking = connection.waypoints[ connection.waypoints.length - 1 ];
snapPoints.add(connection.id + '-docking', docking.original || docking);
}
});
// add sequence flow parents as snap targets
if (is(target, 'bpmn:SequenceFlow')) {
snapPoints = this.addSnapTargetPoints(snapPoints, shape, target.parent);
}
return snapPoints;
};
/**
* @param {Shape} shape
* @param {Shape} target
*
* @return {Shape[]}
*/
BpmnCreateMoveSnapping.prototype.getSnapTargets = function(shape, target) {
return CreateMoveSnapping.prototype.getSnapTargets.call(this, shape, target)
.filter(function(snapTarget) {
// do not snap to lanes
return !is(snapTarget, 'bpmn:Lane');
});
};
// helpers //////////
/**
* @param {Shape} event
* @param {Shape} target
*/
function snapBoundaryEvent(event, target) {
var targetTRBL = asTRBL(target);
var direction = getBoundaryAttachment(event, target);
var context = event.context,
shape = context.shape;
var offset;
if (shape.parent) {
offset = { x: 0, y: 0 };
} else {
offset = getMid(shape);
}
if (/top/.test(direction)) {
setSnapped(event, 'y', targetTRBL.top - offset.y);
} else if (/bottom/.test(direction)) {
setSnapped(event, 'y', targetTRBL.bottom - offset.y);
}
if (/left/.test(direction)) {
setSnapped(event, 'x', targetTRBL.left - offset.x);
} else if (/right/.test(direction)) {
setSnapped(event, 'x', targetTRBL.right - offset.x);
}
}
/**
* @param {Element[]} elements
* @param {string} type
*
* @return {boolean}
*/
function areAll(elements, type) {
return elements.every(function(el) {
return is(el, type);
});
}
/**
* @param {Element} element
*/
function isContainer(element) {
if (is(element, 'bpmn:SubProcess') && isExpanded(element)) {
return true;
}
return is(element, 'bpmn:Participant');
}
/**
* @param {Event} event
*/
function setSnappedIfConstrained(event) {
var context = event.context,
createConstraints = context.createConstraints;
if (!createConstraints) {
return;
}
var top = createConstraints.top,
right = createConstraints.right,
bottom = createConstraints.bottom,
left = createConstraints.left;
if ((left && left >= event.x) || (right && right <= event.x)) {
setSnapped(event, 'x', event.x);
}
if ((top && top >= event.y) || (bottom && bottom <= event.y)) {
setSnapped(event, 'y', event.y);
}
}
function includes(array, value) {
return array.indexOf(value) !== -1;
}
function getDockingSnapOrigin(docking, isMove, event) {
return isMove ? (
{
x: docking.x - event.x,
y: docking.y - event.y
}
) : {
x: docking.x,
y: docking.y
};
}
================================================
FILE: lib/features/snapping/BpmnSnappingUtil.js
================================================
import { getOrientation } from 'diagram-js/lib/layout/LayoutUtil';
/**
* @typedef {import('diagram-js/lib/util/Types').DirectionTRBL} DirectionTRBL
* @typedef {import('diagram-js/lib/util/Types').Point} Point
* @typedef {import('diagram-js/lib/util/Types').Rect} Rect
*/
/**
* @param {Point} position
* @param {Rect} targetBounds
*
* @return {DirectionTRBL|null}
*/
export function getBoundaryAttachment(position, targetBounds) {
var orientation = getOrientation(position, targetBounds, -15);
if (orientation !== 'intersect') {
return orientation;
} else {
return null;
}
}
================================================
FILE: lib/features/snapping/index.js
================================================
import BpmnConnectSnapping from './BpmnConnectSnapping';
import BpmnCreateMoveSnapping from './BpmnCreateMoveSnapping';
import SnappingModule from 'diagram-js/lib/features/snapping';
export default {
__depends__: [ SnappingModule ],
__init__: [
'connectSnapping',
'createMoveSnapping'
],
connectSnapping: [ 'type', BpmnConnectSnapping ],
createMoveSnapping: [ 'type', BpmnCreateMoveSnapping ]
};
================================================
FILE: lib/features/space-tool/BpmnSpaceTool.js
================================================
import inherits from 'inherits-browser';
import SpaceTool from 'diagram-js/lib/features/space-tool/SpaceTool';
import { getEnclosedElements, getBBox } from 'diagram-js/lib/util/Elements';
import { getBusinessObject, is } from '../../util/ModelUtil';
import { isHorizontal } from '../../util/DiUtil';
import { values } from 'min-dash';
/**
* @typedef {import('didi').Injector} Injector
* @typedef {import('diagram-js/lib/core/Canvas').default} Canvas
*
* @typedef {import('../../model/Types').Shape} Shape
*
* @typedef {import('diagram-js/lib/util/Types').Axis} Axis
* @typedef {import('diagram-js/lib/util/Types').Point} Point
*/
/**
* @param {Injector} injector
* @param {Canvas} canvas
*/
export default function BpmnSpaceTool(injector, canvas) {
injector.invoke(SpaceTool, this);
this._canvas = canvas;
}
BpmnSpaceTool.$inject = [
'injector',
'canvas'
];
inherits(BpmnSpaceTool, SpaceTool);
/**
* @param {Shape[]} elements
* @param {Axis} axis
* @param {Point} delta
* @param {number} start
*
* @return {Object}
*/
BpmnSpaceTool.prototype.calculateAdjustments = function(elements, axis, delta, start) {
var canvasRoot = this._canvas.getRootElement(),
spaceRoot = elements[0] === canvasRoot ? null : elements[0],
enclosedArtifacts = [];
// ensure
if (spaceRoot) {
enclosedArtifacts = values(
getEnclosedElements(
canvasRoot.children.filter(
(child) => is(child, 'bpmn:Artifact')
),
getBBox(spaceRoot)
)
);
}
const elementsToMove = [ ...elements, ...enclosedArtifacts ];
var adjustments = SpaceTool.prototype.calculateAdjustments.call(this, elementsToMove, axis, delta, start);
// do not resize:
//
// * text annotations (horizontally/vertically)
// * empty horizontal pools (vertically)
// * empty vertical pools (horizontally)
adjustments.resizingShapes = adjustments.resizingShapes.filter(function(shape) {
if (is(shape, 'bpmn:TextAnnotation')) {
return false;
}
if (isCollapsedPool(shape)) {
if (axis === 'y' && isHorizontal(shape) || axis === 'x' && !isHorizontal(shape)) {
return false;
}
}
return true;
});
return adjustments;
};
// helpers ///////////
function isCollapsedPool(shape) {
return is(shape, 'bpmn:Participant') && !getBusinessObject(shape).processRef;
}
================================================
FILE: lib/features/space-tool/index.js
================================================
import SpaceToolModule from 'diagram-js/lib/features/space-tool';
import BpmnSpaceTool from './BpmnSpaceTool';
export default {
__depends__: [ SpaceToolModule ],
spaceTool: [ 'type', BpmnSpaceTool ]
};
================================================
FILE: lib/import/BpmnImporter.js
================================================
import {
assign
} from 'min-dash';
import { is } from '../util/ModelUtil';
import {
isLabelExternal,
getExternalLabelBounds,
getLabel
} from '../util/LabelUtil';
import {
getMid
} from 'diagram-js/lib/layout/LayoutUtil';
import {
isExpanded
} from '../util/DiUtil';
import {
elementToString
} from './Util';
/**
* @typedef {import('diagram-js/lib/core/Canvas').default} Canvas
* @typedef {import('diagram-js/lib/core/ElementRegistry').default} ElementRegistry
* @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
*
* @typedef {import('../features/modeling/ElementFactory').default} ElementFactory
* @typedef {import('../draw/TextRenderer').default} TextRenderer
*
* @typedef {import('../model/Types').Element} Element
* @typedef {import('../model/Types').Label} Label
* @typedef {import('../model/Types').Shape} Shape
* @typedef {import('../model/Types').Connection} Connection
* @typedef {import('../model/Types').Root} Root
* @typedef {import('../model/Types').ModdleElement} ModdleElement
*/
/**
* @param {ModdleElement} semantic
* @param {ModdleElement} di
* @param {Object} [attrs=null]
*
* @return {Object}
*/
function elementData(semantic, di, attrs) {
return assign({
id: semantic.id,
type: semantic.$type,
businessObject: semantic,
di: di
}, attrs);
}
function getWaypoints(di, source, target) {
var waypoints = di.waypoint;
if (!waypoints || waypoints.length < 2) {
return [ getMid(source), getMid(target) ];
}
return waypoints.map(function(p) {
return { x: p.x, y: p.y };
});
}
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
*
* @param {EventBus} eventBus
* @param {Canvas} canvas
* @param {ElementFactory} elementFactory
* @param {ElementRegistry} elementRegistry
* @param {TextRenderer} textRenderer
*/
export default function BpmnImporter(
eventBus, canvas, elementFactory,
elementRegistry, textRenderer) {
this._eventBus = eventBus;
this._canvas = canvas;
this._elementFactory = elementFactory;
this._elementRegistry = elementRegistry;
this._textRenderer = textRenderer;
}
BpmnImporter.$inject = [
'eventBus',
'canvas',
'elementFactory',
'elementRegistry',
'textRenderer'
];
/**
* Add a BPMN element (semantic) to the canvas making it a child of the
* given parent.
*
* @param {ModdleElement} semantic
* @param {ModdleElement} di
* @param {Shape} parentElement
*
* @return {Shape | Root | Connection}
*/
BpmnImporter.prototype.add = function(semantic, di, parentElement) {
var element,
hidden;
var parentIndex;
// ROOT ELEMENT
// handle the special case that we deal with a
// invisible root element (process, subprocess or collaboration)
if (is(di, 'bpmndi:BPMNPlane')) {
var attrs = is(semantic, 'bpmn:SubProcess')
? { id: semantic.id + '_plane' }
: {};
// add a virtual element (not being drawn)
element = this._elementFactory.createRoot(elementData(semantic, di, attrs));
this._canvas.addRootElement(element);
}
// SHAPE
else if (is(di, 'bpmndi:BPMNShape')) {
var collapsed = !isExpanded(semantic, di),
isFrame = isFrameElement(semantic);
hidden = parentElement && (parentElement.hidden || parentElement.collapsed);
var bounds = di.bounds;
element = this._elementFactory.createShape(elementData(semantic, di, {
collapsed: collapsed,
hidden: hidden,
x: Math.round(bounds.x),
y: Math.round(bounds.y),
width: Math.round(bounds.width),
height: Math.round(bounds.height),
isFrame: isFrame
}));
if (is(semantic, 'bpmn:BoundaryEvent')) {
this._attachBoundary(semantic, element);
}
// insert lanes behind other flow nodes (cf. #727)
if (is(semantic, 'bpmn:Lane')) {
parentIndex = 0;
}
if (is(semantic, 'bpmn:DataStoreReference')) {
// check whether data store is inside our outside of its semantic parent
if (!isPointInsideBBox(parentElement, getMid(bounds))) {
parentElement = this._canvas.findRoot(parentElement);
}
}
this._canvas.addShape(element, parentElement, parentIndex);
}
// CONNECTION
else if (is(di, 'bpmndi:BPMNEdge')) {
var source = this._getSource(semantic),
target = this._getTarget(semantic);
hidden = parentElement && (parentElement.hidden || parentElement.collapsed);
element = this._elementFactory.createConnection(elementData(semantic, di, {
hidden: hidden,
source: source,
target: target,
waypoints: getWaypoints(di, source, target)
}));
if (is(semantic, 'bpmn:DataAssociation')) {
// render always on top; this ensures DataAssociations
// are rendered correctly across different "hacks" people
// love to model such as cross participant / sub process
// associations
parentElement = this._canvas.findRoot(parentElement);
}
this._canvas.addConnection(element, parentElement, parentIndex);
} else {
throw new Error(
`unknown di ${ elementToString(di) } for element ${ elementToString(semantic) }`
);
}
// (optional) LABEL
if (isLabelExternal(semantic) && getLabel(element)) {
this.addLabel(semantic, di, element);
}
this._eventBus.fire('bpmnElement.added', { element: element });
return element;
};
/**
* Attach a boundary element to the given host.
*
* @param {ModdleElement} boundarySemantic
* @param {Shape} 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 a label to a given element.
*
* @param {ModdleElement} semantic
* @param {ModdleElement} di
* @param {Element} element
*
* @return {Label}
*/
BpmnImporter.prototype.addLabel = function(semantic, di, element) {
var bounds,
text,
label;
bounds = getExternalLabelBounds(di, element);
text = getLabel(element);
if (text) {
// get corrected bounds from actual layouted text
bounds = this._textRenderer.getExternalLabelBounds(bounds, text);
}
label = this._elementFactory.createLabel(elementData(semantic, di, {
id: semantic.id + '_label',
labelTarget: element,
type: 'label',
hidden: element.hidden || !getLabel(element),
x: Math.round(bounds.x),
y: Math.round(bounds.y),
width: Math.round(bounds.width),
height: Math.round(bounds.height)
}));
return this._canvas.addShape(label, element.parent);
};
/**
* Get the source or target of the given connection.
*
* @param {ModdleElement} semantic
* @param {'source' | 'target'} side
*
* @return {Element}
*/
BpmnImporter.prototype._getConnectedElement = function(semantic, side) {
var element,
refSemantic,
type = semantic.$type;
refSemantic = semantic[side + 'Ref'];
// handle mysterious isMany DataAssociation#sourceRef
if (side === 'source' && type === 'bpmn:DataInputAssociation') {
refSemantic = refSemantic && refSemantic[0];
}
// fix source / target for DataInputAssociation / DataOutputAssociation
if (side === 'source' && type === 'bpmn:DataOutputAssociation' ||
side === 'target' && type === 'bpmn:DataInputAssociation') {
refSemantic = semantic.$parent;
}
element = refSemantic && this._getElement(refSemantic);
if (element) {
return element;
}
if (refSemantic) {
throw notYetDrawn(semantic, refSemantic, side + 'Ref');
} else {
throw new Error(
`${ elementToString(semantic) }#${ side } Ref not specified`
);
}
};
BpmnImporter.prototype._getSource = function(semantic) {
return this._getConnectedElement(semantic, 'source');
};
BpmnImporter.prototype._getTarget = function(semantic) {
return this._getConnectedElement(semantic, 'target');
};
BpmnImporter.prototype._getElement = function(semantic) {
return this._elementRegistry.get(semantic.id);
};
// helpers ////////////////////
function isPointInsideBBox(bbox, point) {
var x = point.x,
y = point.y;
return x >= bbox.x &&
x <= bbox.x + bbox.width &&
y >= bbox.y &&
y <= bbox.y + bbox.height;
}
function isFrameElement(semantic) {
return is(semantic, 'bpmn:Group');
}
================================================
FILE: lib/import/BpmnTreeWalker.js
================================================
import {
filter,
find,
forEach
} from 'min-dash';
import {
elementToString
} from './Util';
import {
ensureCompatDiRef
} from '../util/CompatibilityUtil';
/**
* @typedef {import('../model/Types').ModdleElement} ModdleElement
*/
/**
* Returns true if an element is of the given meta-model type.
*
* @param {ModdleElement} element
* @param {string} type
*
* @return {boolean}
*/
function is(element, type) {
return element.$instanceOf(type);
}
/**
* Find a suitable display candidate for definitions where the DI does not
* correctly specify one.
*
* @param {ModdleElement} definitions
*
* @return {ModdleElement}
*/
function findDisplayCandidate(definitions) {
return find(definitions.rootElements, function(e) {
return is(e, 'bpmn:Process') || is(e, 'bpmn:Collaboration');
});
}
/**
* @param {Record<'element' | 'root' | 'error', Function>} handler
*/
export default function BpmnTreeWalker(handler) {
// list of containers already walked
var handledElements = {};
// list of elements to handle deferred to ensure
// prerequisites are drawn
var deferred = [];
var diMap = {};
// Helpers //////////////////////
function contextual(fn, ctx) {
return function(e) {
fn(e, ctx);
};
}
function handled(element) {
handledElements[element.id] = element;
}
function isHandled(element) {
return handledElements[element.id];
}
function visit(element, ctx) {
var gfx = element.gfx;
// avoid multiple rendering of elements
if (gfx) {
throw new Error(
`already rendered ${ elementToString(element) }`
);
}
// call handler
return handler.element(element, diMap[element.id], ctx);
}
function visitRoot(element, diagram) {
return handler.root(element, diMap[element.id], diagram);
}
function visitIfDi(element, ctx) {
try {
var gfx = diMap[element.id] && visit(element, ctx);
handled(element);
return gfx;
} catch (error) {
logError(error.message, { element, error });
console.error(`failed to import ${ elementToString(element) }`, error);
}
}
function logError(message, context) {
handler.error(message, context);
}
// DI handling //////////////////////
var registerDi = this.registerDi = function registerDi(di) {
var bpmnElement = di.bpmnElement;
if (bpmnElement) {
if (diMap[bpmnElement.id]) {
logError(
`multiple DI elements defined for ${ elementToString(bpmnElement) }`,
{ element: bpmnElement }
);
} else {
diMap[bpmnElement.id] = di;
ensureCompatDiRef(bpmnElement);
}
} else {
logError(
`no bpmnElement referenced in ${ elementToString(di) }`,
{ element: di }
);
}
};
function handleDiagram(diagram) {
handlePlane(diagram.plane);
}
function handlePlane(plane) {
registerDi(plane);
forEach(plane.planeElement, handlePlaneElement);
}
function handlePlaneElement(planeElement) {
registerDi(planeElement);
}
// Semantic handling //////////////////////
/**
* Handle definitions and return the rendered diagram (if any).
*
* @param {ModdleElement} definitions to walk and import
* @param {ModdleElement} [diagram] specific diagram to import and display
*
* @throws {Error} if no diagram to display could be found
*/
this.handleDefinitions = function handleDefinitions(definitions, diagram) {
// make sure we walk the correct bpmnElement
var diagrams = definitions.diagrams;
if (diagram && diagrams.indexOf(diagram) === -1) {
throw new Error('diagram not part of ');
}
if (!diagram && diagrams && diagrams.length) {
diagram = diagrams[0];
}
// no diagram -> nothing to import
if (!diagram) {
throw new Error('no diagram to display');
}
// load DI from selected diagram only
diMap = {};
handleDiagram(diagram);
var plane = diagram.plane;
if (!plane) {
throw new Error(
`no plane for ${ elementToString(diagram) }`
);
}
var rootElement = plane.bpmnElement;
// ensure we default to a suitable display candidate (process or collaboration),
// even if non is specified in DI
if (!rootElement) {
rootElement = findDisplayCandidate(definitions);
if (!rootElement) {
throw new Error('no process or collaboration to display');
} else {
logError(
`correcting missing bpmnElement on ${ elementToString(plane) } to ${ elementToString(rootElement) }`
);
// correct DI on the fly
plane.bpmnElement = rootElement;
registerDi(plane);
}
}
var ctx = visitRoot(rootElement, plane);
if (is(rootElement, 'bpmn:Process') || is(rootElement, 'bpmn:SubProcess')) {
handleProcess(rootElement, ctx);
} else if (is(rootElement, 'bpmn:Collaboration')) {
handleCollaboration(rootElement, ctx);
// force drawing of everything not yet drawn that is part of the target DI
handleUnhandledProcesses(definitions.rootElements, ctx);
} else {
throw new Error(
`unsupported bpmnElement for ${ elementToString(plane) }: ${ elementToString(rootElement) }`
);
}
// handle all deferred elements
handleDeferred(deferred);
};
var handleDeferred = this.handleDeferred = function handleDeferred() {
var fn;
// drain deferred until empty
while (deferred.length) {
fn = deferred.shift();
fn();
}
};
function handleProcess(process, context) {
handleFlowElementsContainer(process, context);
handleIoSpecification(process.ioSpecification, context);
handleArtifacts(process.artifacts, context);
// log process handled
handled(process);
}
function handleUnhandledProcesses(rootElements, ctx) {
// walk through all processes that have not yet been drawn and draw them
// if they contain lanes with DI information.
// we do this to pass the free-floating lane test cases in the MIWG test suite
var processes = filter(rootElements, function(e) {
return !isHandled(e) && is(e, 'bpmn:Process') && e.laneSets;
});
processes.forEach(contextual(handleProcess, ctx));
}
function handleMessageFlow(messageFlow, context) {
visitIfDi(messageFlow, context);
}
function handleMessageFlows(messageFlows, context) {
forEach(messageFlows, contextual(handleMessageFlow, context));
}
function handleDataAssociation(association, context) {
visitIfDi(association, context);
}
function handleDataInput(dataInput, context) {
visitIfDi(dataInput, context);
}
function handleDataOutput(dataOutput, context) {
visitIfDi(dataOutput, context);
}
function handleArtifact(artifact, context) {
// bpmn:TextAnnotation
// bpmn:Group
// bpmn:Association
visitIfDi(artifact, context);
}
function handleArtifacts(artifacts, context) {
forEach(artifacts, function(e) {
if (is(e, 'bpmn:Association')) {
deferred.push(function() {
handleArtifact(e, context);
});
} else {
handleArtifact(e, context);
}
});
}
function handleIoSpecification(ioSpecification, context) {
if (!ioSpecification) {
return;
}
forEach(ioSpecification.dataInputs, contextual(handleDataInput, context));
forEach(ioSpecification.dataOutputs, contextual(handleDataOutput, context));
}
var handleSubProcess = this.handleSubProcess = function handleSubProcess(subProcess, context) {
handleFlowElementsContainer(subProcess, context);
handleArtifacts(subProcess.artifacts, context);
};
function handleFlowNode(flowNode, context) {
var childCtx = visitIfDi(flowNode, context);
if (is(flowNode, 'bpmn:SubProcess')) {
handleSubProcess(flowNode, childCtx || context);
}
if (is(flowNode, 'bpmn:Activity')) {
handleIoSpecification(flowNode.ioSpecification, context);
}
// defer handling of associations
// affected types:
//
// * bpmn:Activity
// * bpmn:ThrowEvent
// * bpmn:CatchEvent
//
deferred.push(function() {
forEach(flowNode.dataInputAssociations, contextual(handleDataAssociation, context));
forEach(flowNode.dataOutputAssociations, contextual(handleDataAssociation, context));
});
}
function handleSequenceFlow(sequenceFlow, context) {
visitIfDi(sequenceFlow, context);
}
function handleDataElement(dataObject, context) {
visitIfDi(dataObject, context);
}
function handleLane(lane, context) {
deferred.push(function() {
var newContext = visitIfDi(lane, context);
if (lane.childLaneSet) {
handleLaneSet(lane.childLaneSet, newContext || context);
}
wireFlowNodeRefs(lane);
});
}
function handleLaneSet(laneSet, context) {
forEach(laneSet.lanes, contextual(handleLane, context));
}
function handleLaneSets(laneSets, context) {
forEach(laneSets, contextual(handleLaneSet, context));
}
function handleFlowElementsContainer(container, context) {
handleFlowElements(container.flowElements, context);
if (container.laneSets) {
handleLaneSets(container.laneSets, context);
}
}
function handleFlowElements(flowElements, context) {
forEach(flowElements, function(flowElement) {
if (is(flowElement, 'bpmn:SequenceFlow')) {
deferred.push(function() {
handleSequenceFlow(flowElement, context);
});
} else if (is(flowElement, 'bpmn:BoundaryEvent')) {
deferred.unshift(function() {
handleFlowNode(flowElement, context);
});
} else if (is(flowElement, 'bpmn:FlowNode')) {
handleFlowNode(flowElement, context);
} else if (is(flowElement, 'bpmn:DataObject')) {
// SKIP (assume correct referencing via DataObjectReference)
} else if (is(flowElement, 'bpmn:DataStoreReference')) {
handleDataElement(flowElement, context);
} else if (is(flowElement, 'bpmn:DataObjectReference')) {
handleDataElement(flowElement, context);
} else {
logError(
`unrecognized flowElement ${ elementToString(flowElement) } in context ${ elementToString(context && context.businessObject) }`,
{
element: flowElement,
context
}
);
}
});
}
function handleParticipant(participant, context) {
var newCtx = visitIfDi(participant, context);
var process = participant.processRef;
if (process) {
handleProcess(process, newCtx || context);
}
}
function handleCollaboration(collaboration, context) {
forEach(collaboration.participants, contextual(handleParticipant, context));
deferred.push(function() {
handleMessageFlows(collaboration.messageFlows, context);
});
handleArtifacts(collaboration.artifacts, context);
}
function wireFlowNodeRefs(lane) {
// wire the virtual flowNodeRefs <-> relationship
forEach(lane.flowNodeRef, function(flowNode) {
var lanes = flowNode.get('lanes');
if (lanes) {
lanes.push(lane);
}
});
}
}
================================================
FILE: lib/import/Importer.js
================================================
import {
find,
forEach,
map
} from 'min-dash';
import BpmnTreeWalker from './BpmnTreeWalker';
import { is } from '../util/ModelUtil';
/**
* @typedef {import('../model/Types').ModdleElement} ModdleElement
*
* @typedef { {
* warnings: string[];
* } } ImportBPMNDiagramResult
*
* @typedef {ImportBPMNDiagramResult & Error} ImportBPMNDiagramError
*/
/**
* Import the definitions into a diagram.
*
* Errors and warnings are reported through the specified callback.
*
* @param {ModdleElement} diagram
* @param {ModdleElement} definitions
* @param {ModdleElement} [bpmnDiagram] The diagram to be rendered (if not
* provided, the first one will be rendered).
*
* @return {Promise}
*/
export function importBpmnDiagram(diagram, definitions, bpmnDiagram) {
var importer,
eventBus,
canvas;
var error,
warnings = [];
/**
* Walk the diagram semantically, importing (=drawing)
* all elements you encounter.
*
* @param {ModdleElement} definitions
* @param {ModdleElement} bpmnDiagram
*/
function render(definitions, bpmnDiagram) {
var visitor = {
root: function(element, di) {
return importer.add(element, di);
},
element: function(element, di, parentShape) {
return importer.add(element, di, parentShape);
},
error: function(message, context) {
warnings.push({ message: message, context: context });
}
};
var walker = new BpmnTreeWalker(visitor);
bpmnDiagram = bpmnDiagram || (definitions.diagrams && definitions.diagrams[0]);
var diagramsToImport = getDiagramsToImport(definitions, bpmnDiagram);
if (!diagramsToImport) {
throw new Error('no diagram to display');
}
// traverse BPMN 2.0 document model,
// starting at definitions
forEach(diagramsToImport, function(diagram) {
walker.handleDefinitions(definitions, diagram);
});
var rootId = bpmnDiagram.plane.bpmnElement.id;
// we do need to account for different ways we create root elements
// each nested imported do have the `_plane` suffix, while
// the root is found under the business object ID
canvas.setRootElement(
canvas.findRoot(rootId + '_plane') || canvas.findRoot(rootId)
);
}
return new Promise(function(resolve, reject) {
try {
importer = diagram.get('bpmnImporter');
eventBus = diagram.get('eventBus');
canvas = diagram.get('canvas');
eventBus.fire('import.render.start', { definitions: definitions });
render(definitions, bpmnDiagram);
eventBus.fire('import.render.complete', {
error: error,
warnings: warnings
});
return resolve({ warnings: warnings });
} catch (e) {
e.warnings = warnings;
return reject(e);
}
});
}
/**
* Returns all diagrams in the same hierarchy as the requested diagram.
* Includes all parent and sub process diagrams.
*
* @param {ModdleElement} definitions
* @param {ModdleElement} bpmnDiagram
*
* @return {ModdleElement[]}
*/
function getDiagramsToImport(definitions, bpmnDiagram) {
if (!bpmnDiagram || !bpmnDiagram.plane) {
return;
}
var bpmnElement = bpmnDiagram.plane.bpmnElement,
rootElement = bpmnElement;
if (!is(bpmnElement, 'bpmn:Process') && !is(bpmnElement, 'bpmn:Collaboration')) {
rootElement = findRootProcess(bpmnElement);
}
// in case the process is part of a collaboration, the plane references the
// collaboration, not the process
var collaboration;
if (is(rootElement, 'bpmn:Collaboration')) {
collaboration = rootElement;
} else {
collaboration = find(definitions.rootElements, function(element) {
if (!is(element, 'bpmn:Collaboration')) {
return;
}
return find(element.participants, function(participant) {
return participant.processRef === rootElement;
});
});
}
var rootElements = [ rootElement ];
// all collaboration processes can contain sub-diagrams
if (collaboration) {
rootElements = map(collaboration.participants, function(participant) {
return participant.processRef;
});
rootElements.push(collaboration);
}
var allChildren = selfAndAllFlowElements(rootElements);
// if we have multiple diagrams referencing the same element, we
// use the first in the file
var diagramsToImport = [ bpmnDiagram ];
var handledElements = [ bpmnElement ];
forEach(definitions.diagrams, function(diagram) {
if (!diagram.plane) {
return;
}
var businessObject = diagram.plane.bpmnElement;
if (
allChildren.indexOf(businessObject) !== -1 &&
handledElements.indexOf(businessObject) === -1
) {
diagramsToImport.push(diagram);
handledElements.push(businessObject);
}
});
return diagramsToImport;
}
function selfAndAllFlowElements(elements) {
var result = [];
forEach(elements, function(element) {
if (!element) {
return;
}
result.push(element);
result = result.concat(selfAndAllFlowElements(element.flowElements));
});
return result;
}
function findRootProcess(element) {
var parent = element;
while (parent) {
if (is(parent, 'bpmn:Process')) {
return parent;
}
parent = parent.$parent;
}
}
================================================
FILE: lib/import/Util.js
================================================
export function elementToString(e) {
if (!e) {
return '';
}
return '<' + e.$type + (e.id ? ' id="' + e.id : '') + '" />';
}
================================================
FILE: lib/import/index.js
================================================
import translate from 'diagram-js/lib/i18n/translate';
import BpmnImporter from './BpmnImporter';
export default {
__depends__: [
translate
],
bpmnImporter: [ 'type', BpmnImporter ]
};
================================================
FILE: lib/index.js
================================================
export {
default
} from './Viewer';
================================================
FILE: lib/model/Types.ts
================================================
import type {
Connection as BaseConnection,
Element as BaseElement,
Label as BaseLabel,
Root as BaseRoot,
Shape as BaseShape
} from "diagram-js/lib/model";
export type Moddle = any;
export type ModdleElement = any;
export type ModdleExtension = {};
export type BpmnAttributes = {
associationDirection: 'None' | 'One' | 'Both';
cancelActivity: boolean;
eventDefinitionType: string;
isExpanded: boolean;
isHorizontal: boolean;
isForCompensation: boolean;
isInterrupting: boolean;
processRef: ModdleElement;
triggeredByEvent: boolean;
};
export type Element = {
businessObject: any;
di: any;
type: string;
} & BaseElement;
export type Connection = BaseConnection & Element;
export type Label = BaseLabel & Element;
export type Root = BaseRoot & Element;
export type Shape = BaseShape & Element;
export type Parent = Root | Shape;
================================================
FILE: lib/util/AnnotationUtil.js
================================================
import { forEach } from 'min-dash';
import { selfAndChildren } from 'diagram-js/lib/util/Elements';
import { is } from './ModelUtil';
/**
* @typedef { import('../model/Types').Element } Element
*/
/**
* Get text annotations connected to the given element.
*
* @param {Element} element
*
* @return { { annotation: Element, association: Element }[] }
*/
export function getElementAnnotations(element) {
let result = [];
forEach(element.incoming, (connection) => {
if (is(connection, 'bpmn:Association') && is(connection.source, 'bpmn:TextAnnotation')) {
result.push({ annotation: connection.source, association: connection });
}
});
forEach(element.outgoing, (connection) => {
if (is(connection, 'bpmn:Association') && is(connection.target, 'bpmn:TextAnnotation')) {
result.push({ annotation: connection.target, association: connection });
}
});
return result;
}
/**
* Recursively collect text annotations connected to the given elements and their descendants.
* De-duplicates by annotation, collecting all associations per annotation.
*
* @param {Element[]} elements
*
* @return { { annotation: Element, associations: Element[] }[] }
*/
export function collectElementsAnnotations(elements) {
const result = new Map();
forEach(selfAndChildren(elements, true, -1), (element) => {
forEach(getElementAnnotations(element), (entry) => {
if (!result.has(entry.annotation)) {
result.set(entry.annotation, { annotation: entry.annotation, associations: [] });
}
result.get(entry.annotation).associations.push(entry.association);
});
});
return [ ...result.values() ];
}
================================================
FILE: lib/util/CompatibilityUtil.js
================================================
import {
has
} from 'min-dash';
/**
* @typedef {import('../model/Types').ModdleElement} ModdleElement
*/
// TODO(nikku): remove with future bpmn-js version
var DI_ERROR_MESSAGE = 'Tried to access di from the businessObject. The di is available through the diagram element only. For more information, see https://github.com/bpmn-io/bpmn-js/issues/1472';
/**
* @private
*
* @param {ModdleElement} businessObject
*/
export function ensureCompatDiRef(businessObject) {
// bpmnElement can have multiple independent DIs
if (!has(businessObject, 'di')) {
Object.defineProperty(businessObject, 'di', {
enumerable: false,
get: function() {
throw new Error(DI_ERROR_MESSAGE);
}
});
}
}
================================================
FILE: lib/util/DiUtil.js
================================================
import {
is,
getBusinessObject,
getDi
} from './ModelUtil';
import {
some
} from 'min-dash';
/**
* @typedef {import('../model/Types').Element} Element
* @typedef {import('../model/Types').ModdleElement} ModdleElement
*/
/**
* @param {Element} element
* @param {ModdleElement} [di]
*
* @return {boolean}
*/
export function isExpanded(element, di) {
if (is(element, 'bpmn:CallActivity')) {
return false;
}
if (is(element, 'bpmn:SubProcess')) {
di = di || getDi(element);
if (di && is(di, 'bpmndi:BPMNPlane')) {
return true;
}
return di && !!di.isExpanded;
}
if (is(element, 'bpmn:Participant')) {
return !!getBusinessObject(element).processRef;
}
return true;
}
/**
* @param {Element} element
*
* @return {boolean}
*/
export function isHorizontal(element) {
if (!is(element, 'bpmn:Participant') && !is(element, 'bpmn:Lane')) {
return undefined;
}
var isHorizontal = getDi(element).isHorizontal;
if (isHorizontal === undefined) {
return true;
}
return isHorizontal;
}
/**
* @param {Element} element
*
* @return {boolean}
*/
export function isInterrupting(element) {
return element && getBusinessObject(element).isInterrupting !== false;
}
/**
* @param {Element} element
*
* @return {boolean}
*/
export function isEventSubProcess(element) {
return element && !!getBusinessObject(element).triggeredByEvent;
}
/**
* @param {Element} element
* @param {string} eventType
*
* @return {boolean}
*/
export function hasEventDefinition(element, eventType) {
var eventDefinitions = getBusinessObject(element).eventDefinitions;
return some(eventDefinitions, function(event) {
return is(event, eventType);
});
}
/**
* @param {Element} element
*
* @return {boolean}
*/
export function hasErrorEventDefinition(element) {
return hasEventDefinition(element, 'bpmn:ErrorEventDefinition');
}
/**
* @param {Element} element
*
* @return {boolean}
*/
export function hasEscalationEventDefinition(element) {
return hasEventDefinition(element, 'bpmn:EscalationEventDefinition');
}
/**
* @param {Element} element
*
* @return {boolean}
*/
export function hasCompensateEventDefinition(element) {
return hasEventDefinition(element, 'bpmn:CompensateEventDefinition');
}
================================================
FILE: lib/util/DrilldownUtil.js
================================================
import { getDi, is } from './ModelUtil';
/**
* @typedef {import('../model/Types').Element} Element
* @typedef {import('../model/Types').ModdleElement} ModdleElement
*/
export var planeSuffix = '_plane';
/**
* Get primary shape ID for a plane.
*
* @param {Element|ModdleElement} element
*
* @return {string}
*/
export function getShapeIdFromPlane(element) {
var id = element.id;
return removePlaneSuffix(id);
}
/**
* Get plane ID for a primary shape.
*
* @param {Element|ModdleElement} element
*
* @return {string}
*/
export function getPlaneIdFromShape(element) {
var id = element.id;
if (is(element, 'bpmn:SubProcess')) {
return addPlaneSuffix(id);
}
return id;
}
/**
* Get plane ID for primary shape ID.
*
* @param {string} id
*
* @return {string}
*/
export function toPlaneId(id) {
return addPlaneSuffix(id);
}
/**
* Check wether element is plane.
*
* @param {Element|ModdleElement} element
*
* @return {boolean}
*/
export function isPlane(element) {
var di = getDi(element);
return is(di, 'bpmndi:BPMNPlane');
}
function addPlaneSuffix(id) {
return id + planeSuffix;
}
function removePlaneSuffix(id) {
return id.replace(new RegExp(planeSuffix + '$'), '');
}
================================================
FILE: lib/util/LabelUtil.js
================================================
import {
assign
} from 'min-dash';
import { is } from './ModelUtil';
import { isLabel } from 'diagram-js/lib/util/ModelUtil';
export { isLabel } from 'diagram-js/lib/util/ModelUtil';
/**
* @typedef {import('diagram-js/lib/util/Types').Point} Point
* @typedef {import('diagram-js/lib/util/Types').Rect} Rect
*
* @typedef {import('../model/Types').Element} Element
* @typedef {import('../model/Types').ModdleElement} ModdleElement
*/
export var DEFAULT_LABEL_SIZE = {
width: 90,
height: 20
};
export var FLOW_LABEL_INDENT = 15;
/**
* Return true if the given semantic has an external label.
*
* @param {Element} semantic
*
* @return {boolean}
*/
export function isLabelExternal(semantic) {
return is(semantic, 'bpmn:Event') ||
is(semantic, 'bpmn:Gateway') ||
is(semantic, 'bpmn:DataStoreReference') ||
is(semantic, 'bpmn:DataObjectReference') ||
is(semantic, 'bpmn:DataInput') ||
is(semantic, 'bpmn:DataOutput') ||
is(semantic, 'bpmn:SequenceFlow') ||
is(semantic, 'bpmn:MessageFlow') ||
is(semantic, 'bpmn:Group');
}
/**
* Return true if the given element has an external label.
*
* @param {Element} element
*
* @return {boolean}
*/
export function hasExternalLabel(element) {
return isLabel(element.label);
}
/**
* Get the position of a sequence flow label.
*
* @param {Point[]} waypoints
*
* @return {Point}
*/
export function getFlowLabelPosition(waypoints) {
// get the waypoints mid
var mid = waypoints.length / 2 - 1;
var first = waypoints[Math.floor(mid)];
var second = waypoints[Math.ceil(mid + 0.01)];
// get position
var position = getWaypointsMid(waypoints);
// calculate angle
var angle = Math.atan((second.y - first.y) / (second.x - first.x));
var x = position.x,
y = position.y;
if (Math.abs(angle) < Math.PI / 2) {
y -= FLOW_LABEL_INDENT;
} else {
x += FLOW_LABEL_INDENT;
}
return { x: x, y: y };
}
/**
* Get the middle of a number of waypoints.
*
* @param {Point[]} waypoints
*
* @return {Point}
*/
export function getWaypointsMid(waypoints) {
var mid = waypoints.length / 2 - 1;
var first = waypoints[Math.floor(mid)];
var second = waypoints[Math.ceil(mid + 0.01)];
return {
x: first.x + (second.x - first.x) / 2,
y: first.y + (second.y - first.y) / 2
};
}
/**
* Get the middle of the external label of an element.
*
* @param {Element} element
*
* @return {Point}
*/
export function getExternalLabelMid(element) {
if (element.waypoints) {
return getFlowLabelPosition(element.waypoints);
} else if (is(element, 'bpmn:Group')) {
return {
x: element.x + element.width / 2,
y: element.y + DEFAULT_LABEL_SIZE.height / 2
};
} else {
return {
x: element.x + element.width / 2,
y: element.y + element.height + DEFAULT_LABEL_SIZE.height / 2
};
}
}
/**
* Return the bounds of an elements label, parsed from the elements DI or
* generated from its bounds.
*
* @param {ModdleElement} di
* @param {Element} element
*
* @return {Rect}
*/
export function getExternalLabelBounds(di, element) {
var mid,
size,
bounds,
label = di.label;
if (label && label.bounds) {
bounds = label.bounds;
size = {
width: Math.max(DEFAULT_LABEL_SIZE.width, bounds.width),
height: bounds.height
};
mid = {
x: bounds.x + bounds.width / 2,
y: bounds.y + bounds.height / 2
};
} else {
mid = getExternalLabelMid(element);
size = DEFAULT_LABEL_SIZE;
}
return assign({
x: mid.x - size.width / 2,
y: mid.y - size.height / 2
}, size);
}
/**
* @param {ModdleElement} semantic
*
* @returns {string}
*/
function getLabelAttr(semantic) {
if (
is(semantic, 'bpmn:FlowElement') ||
is(semantic, 'bpmn:Participant') ||
is(semantic, 'bpmn:Lane') ||
is(semantic, 'bpmn:SequenceFlow') ||
is(semantic, 'bpmn:MessageFlow') ||
is(semantic, 'bpmn:DataInput') ||
is(semantic, 'bpmn:DataOutput')
) {
return 'name';
}
if (is(semantic, 'bpmn:TextAnnotation')) {
return 'text';
}
if (is(semantic, 'bpmn:Group')) {
return 'categoryValueRef';
}
}
/**
* @param {ModdleElement} semantic
*
* @returns {string}
*/
function getCategoryValue(semantic) {
var categoryValueRef = semantic['categoryValueRef'];
if (!categoryValueRef) {
return '';
}
return categoryValueRef.value || '';
}
/**
* @param {Element} element
*
* @return {string}
*/
export function getLabel(element) {
var semantic = element.businessObject,
attr = getLabelAttr(semantic);
if (attr) {
if (attr === 'categoryValueRef') {
return getCategoryValue(semantic);
}
return semantic[attr] || '';
}
}
/**
* @param {Element} element
* @param {string} text
*
* @return {Element}
*/
export function setLabel(element, text) {
var semantic = element.businessObject,
attr = getLabelAttr(semantic);
if (attr) {
if (attr === 'categoryValueRef') {
if (!semantic[attr]) {
return element;
}
semantic[attr].value = text;
} else {
semantic[attr] = text;
}
}
return element;
}
/**
* Returns true if the given element is an external label.
*
* @param {Element} element
*
* @return {boolean}
*/
export function isExternalLabel(element) {
return isLabel(element) && isLabelExternal(element.labelTarget);
}
================================================
FILE: lib/util/ModelUtil.js
================================================
import {
some
} from 'min-dash';
/**
* @typedef { import('../model/Types').Element } Element
* @typedef { import('../model/Types').ModdleElement } ModdleElement
*/
/**
* Is an element of the given BPMN type?
*
* @param {Element|ModdleElement} element
* @param {string} type
*
* @return {boolean}
*/
export function is(element, type) {
var bo = getBusinessObject(element);
return bo && (typeof bo.$instanceOf === 'function') && bo.$instanceOf(type);
}
/**
* Return true if element has any of the given types.
*
* @param {Element|ModdleElement} element
* @param {string[]} types
*
* @return {boolean}
*/
export function isAny(element, types) {
return some(types, function(t) {
return is(element, t);
});
}
/**
* Return the business object for a given element.
*
* @param {Element|ModdleElement} element
*
* @return {ModdleElement}
*/
export function getBusinessObject(element) {
return (element && element.businessObject) || element;
}
/**
* Return the di object for a given element.
*
* @param {Element} element
*
* @return {ModdleElement}
*/
export function getDi(element) {
return element && element.di;
}
================================================
FILE: lib/util/PoweredByUtil.js
================================================
/**
* This file must not be changed or exchanged.
*
* @see http://bpmn.io/license for more information.
*/
import {
assignStyle,
domify,
delegate as domDelegate,
query as domQuery
} from 'min-dom';
// inlined ../../resources/logo.svg
var BPMNIO_LOGO_SVG = ' ';
export var BPMNIO_IMG = BPMNIO_LOGO_SVG;
export var LOGO_STYLES = {
verticalAlign: 'middle'
};
export var LINK_STYLES = {
'color': '#404040'
};
var LIGHTBOX_STYLES = {
'zIndex': '1001',
'position': 'fixed',
'top': '0',
'left': '0',
'right': '0',
'bottom': '0'
};
var BACKDROP_STYLES = {
'width': '100%',
'height': '100%',
'background': 'rgba(40,40,40,0.2)'
};
var NOTICE_STYLES = {
'position': 'absolute',
'left': '50%',
'top': '40%',
'transform': 'translate(-50%)',
'width': '260px',
'padding': '10px',
'background': 'white',
'boxShadow': '0 1px 4px rgba(0,0,0,0.3)',
'fontFamily': 'Helvetica, Arial, sans-serif',
'fontSize': '14px',
'display': 'flex',
'lineHeight': '1.3'
};
var LIGHTBOX_MARKUP =
'';
var lightbox;
function createLightbox() {
lightbox = domify(LIGHTBOX_MARKUP);
assignStyle(lightbox, LIGHTBOX_STYLES);
assignStyle(domQuery('svg', lightbox), LOGO_STYLES);
assignStyle(domQuery('.backdrop', lightbox), BACKDROP_STYLES);
assignStyle(domQuery('.notice', lightbox), NOTICE_STYLES);
assignStyle(domQuery('.link', lightbox), LINK_STYLES, {
'margin': '15px 20px 15px 10px',
'alignSelf': 'center'
});
}
export function open() {
if (!lightbox) {
createLightbox();
domDelegate.bind(lightbox, '.backdrop', 'click', function(event) {
document.body.removeChild(lightbox);
});
}
document.body.appendChild(lightbox);
}
================================================
FILE: lib/util/Types.js
================================================
/**
* @typedef {Object} Colors
* @property {string} [fill]
* @property {string} [stroke]
*/
================================================
FILE: lib/util/Types.ts
================================================
export type Colors = {
fill?: string;
stroke?: string;
};
================================================
FILE: package.json
================================================
{
"name": "bpmn-js",
"version": "18.13.1",
"description": "A bpmn 2.0 toolkit and web modeler",
"main": "lib/index.js",
"files": [
"dist",
"lib",
"test/util",
"test/helper",
"test/matchers",
"!.eslintrc",
"!lib/**/*.spec.ts"
],
"scripts": {
"all": "run-s lint test generate-types distro test:distro",
"lint": "eslint .",
"format": "run-s format:markdown 'lint -- --fix'",
"format:markdown": "remark . -qo",
"start": "cross-env SINGLE_START=modeler npm run dev",
"start:viewer": "cross-env SINGLE_START=viewer npm run dev",
"start:navigated-viewer": "cross-env SINGLE_START=navigated-viewer npm run dev",
"dev": "npm test -- --auto-watch --no-single-run",
"test": "karma start test/config/karma.unit.js",
"distro": "node tasks/build-distro.mjs",
"collect-translations": "cross-env COLLECT_TRANSLATIONS=1 npm test",
"generate-types": "run-s generate-types:*",
"generate-types:generate": "del-cli \"lib/**/*.d.ts\" && npx bio-dts -r lib",
"generate-types:test": "tsc --noEmit --noImplicitAny",
"test:distro": "node tasks/test-distro.mjs",
"postversion": "run-s distro test:distro",
"prepare": "run-s distro",
"prepublishOnly": "run-s generate-types"
},
"engines": {
"node": "*"
},
"repository": {
"type": "git",
"url": "https://github.com/bpmn-io/bpmn-js"
},
"keywords": [
"bpmn",
"bpmn-js",
"toolkit",
"web modeler",
"modeler",
"modeling",
"process modeling"
],
"author": {
"name": "Nico Rehwaldt",
"url": "https://github.com/nikku"
},
"contributors": [
{
"name": "bpmn.io contributors",
"url": "https://github.com/bpmn-io"
}
],
"license": "SEE LICENSE IN LICENSE",
"sideEffects": [
"*.css"
],
"devDependencies": {
"@babel/core": "^7.28.5",
"@bpmn-io/a11y": "^0.1.0",
"@rollup/plugin-commonjs": "^29.0.0",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^16.0.3",
"@rollup/plugin-replace": "^6.0.3",
"@rollup/plugin-terser": "^1.0.0",
"babel-loader": "^10.0.0",
"babel-plugin-istanbul": "^7.0.1",
"bio-dts": "^0.11.0",
"bpmn-font": "^0.12.1",
"camunda-bpmn-moddle": "^4.0.1",
"chai": "4.1.2",
"chai-match": "^1.1.1",
"cpy": "^13.0.0",
"cross-env": "^10.0.0",
"del": "^8.0.0",
"del-cli": "^7.0.0",
"eslint": "^9.39.2",
"eslint-plugin-bpmn-io": "^2.2.0",
"execa": "^9.0.0",
"file-drops": "^0.7.0",
"karma": "^6.4.4",
"karma-chrome-launcher-2": "^3.3.0",
"karma-coverage": "^2.2.0",
"karma-debug-launcher": "^0.0.5",
"karma-env-preprocessor": "^0.1.1",
"karma-firefox-launcher": "^2.1.3",
"karma-mocha": "^2.0.1",
"karma-safari-launcher": "^1.0.0",
"karma-sinon-chai": "^2.0.2",
"karma-webpack": "^5.0.1",
"mocha": "^10.8.2",
"mocha-test-container-support": "0.2.0",
"npm-run-all2": "^8.0.4",
"puppeteer": "~24.36.0",
"remark-cli": "^12.0.1",
"remark-preset-bpmn-io": "^0.4.0",
"rollup": "^4.55.1",
"rollup-plugin-license": "^3.6.0",
"sinon": "^17.0.1",
"sinon-chai": "^3.7.0",
"ts-expect": "^1.3.0",
"typescript": "^5.9.3",
"webpack": "^5.104.1"
},
"dependencies": {
"bpmn-moddle": "^10.0.0",
"diagram-js": "^15.10.0",
"diagram-js-direct-editing": "^3.3.0",
"ids": "^3.0.0",
"inherits-browser": "^0.1.0",
"min-dash": "^5.0.0",
"min-dom": "^5.2.0",
"tiny-svg": "^4.1.4"
},
"remarkConfig": {
"plugins": [
"preset-bpmn-io",
[
"lint-no-html",
false
]
]
}
}
================================================
FILE: renovate.json
================================================
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"github>bpmn-io/renovate-config:recommended"
]
}
================================================
FILE: resources/banner-min.txt
================================================
/*! bpmn-js - {{ name }} v{{ version }} | Copyright (c) 2014-present, camunda Services GmbH | bpmn.io/license */
================================================
FILE: resources/banner.txt
================================================
/*!
* bpmn-js - {{ name }} v{{ version }}
*
* Copyright (c) 2014-present, camunda Services GmbH
*
* Released under the bpmn.io license
* http://bpmn.io/license
*
* Source Code: https://github.com/bpmn-io/bpmn-js
*
* Date: {{ date }}
*/
================================================
FILE: resources/initial.bpmn
================================================
================================================
FILE: rollup.config.js
================================================
import terser from '@rollup/plugin-terser';
import nodeResolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import json from '@rollup/plugin-json';
import replace from '@rollup/plugin-replace';
import license from 'rollup-plugin-license';
import {
readFileSync
} from 'fs';
import pkg from './package.json';
const outputDir = 'dist';
const distros = [
{
input: 'Viewer',
output: 'bpmn-viewer'
},
{
input: 'NavigatedViewer',
output: 'bpmn-navigated-viewer'
},
{
input: 'Modeler',
output: 'bpmn-modeler'
}
];
const configs = distros.reduce(function(configs, distro) {
const {
input,
output
} = distro;
return [
...configs,
{
input: `./lib/${input}.js`,
output: {
name: 'BpmnJS',
file: `${outputDir}/${output}.development.js`,
format: 'umd'
},
plugins: pgl([
banner(output)
], 'development')
},
{
input: `./lib/${input}.js`,
output: {
name: 'BpmnJS',
file: `${outputDir}/${output}.production.min.js`,
format: 'umd'
},
plugins: pgl([
banner(output, true),
terser({
output: {
comments: /license|@preserve/
}
})
], 'production')
}
];
}, []);
export default configs;
// helpers //////////////////////
function banner(bundleName, minified) {
const bannerName = (
minified
? 'banner-min'
: 'banner'
);
const bannerTemplate = readFileSync(`${__dirname}/resources/${bannerName}.txt`, 'utf8');
const banner = processTemplate(bannerTemplate, {
version: pkg.version,
date: today(),
name: bundleName
});
return license({
banner
});
}
function pgl(plugins = [], env = 'production') {
return [
replace({
preventAssignment: true,
'process.env.NODE_ENV': JSON.stringify(env)
}),
nodeResolve(),
commonjs(),
json(),
...plugins
];
}
function pad(n) {
if (n < 10) {
return '0' + n;
} else {
return n;
}
}
function today() {
const d = new Date();
return [
d.getFullYear(),
pad(d.getMonth() + 1),
pad(d.getDate())
].join('-');
}
function processTemplate(str, args) {
return str.replace(/\{\{\s*([^\s]+)\s*\}\}/g, function(_, n) {
var replacement = args[n];
if (!replacement) {
throw new Error(`unknown template {{ ${ n } }}`);
}
return replacement;
});
}
================================================
FILE: tasks/build-distro.mjs
================================================
import path from 'node:path';
import fs from 'node:fs';
import cp from 'cpy';
import { deleteAsync as del } from 'del';
import { execa as exec } from 'execa';
import { createRequire } from 'node:module';
var dest = process.env.DISTRO_DIST || 'dist';
function resolve(module, sub) {
var require = createRequire(import.meta.url);
var pkg = require.resolve(module + '/package.json');
return path.dirname(pkg) + sub;
}
async function run() {
console.log('clean ' + dest);
await del(dest);
console.log('mkdir -p ' + dest);
fs.mkdirSync(dest, { recursive: true });
console.log('copy bpmn-font to ' + dest + '/bpmn-font');
await cp(resolve('bpmn-font', '/dist/css/**'), dest + '/assets/bpmn-font/css');
await cp(resolve('bpmn-font', '/dist/font/**'), dest + '/assets/bpmn-font/font');
console.log('copy diagram-js.css to ' + dest);
await cp(resolve('diagram-js', '/assets/**'), dest + '/assets');
console.log('copy bpmn-js.css to ' + dest);
await cp('./assets/*.css', dest + '/assets');
console.log('building pre-packaged distributions');
await exec('rollup', [ '-c', '--bundleConfigAsCjs' ], {
stdio: 'inherit'
});
console.log('done.');
}
run().catch(e => {
console.error('failed to build distribution', e);
process.exit(1);
});
================================================
FILE: tasks/stages/await-published
================================================
#!/bin/bash
set -eo pipefail
shopt -s inherit_errexit nullglob
i=0
tries=9
pkg="$PKG"
until [ $i -gt $tries ]
do
echo "Checking for $pkg in npm registry ($((i+1))/$((tries+1)))"
info=$(npm info $pkg 2> /dev/null || echo "FAILED")
if [[ "$info" != "FAILED" ]]; then
echo "Found."
exit 0
fi
i=$(($i+1))
sleep 5s
done
echo "Not found after $i tries. Giving up."
exit 1;
================================================
FILE: tasks/stages/update-demo
================================================
#!/bin/bash
set -eo pipefail
shopt -s inherit_errexit nullglob
# bumps bpmn-js and diagram-js dependencies in bpmn-io-demo
PWD="$(pwd)"
WORKDIR="$(pwd)/tmp"
CLONE_DIR="$WORKDIR/bpmn-io-demo"
# create work dir
mkdir -p "$WORKDIR"
git clone --depth=1 "https://$BPMN_IO_TOKEN@github.com/$BPMN_IO_DEMO_ENDPOINT.git" "$CLONE_DIR"
cd "$CLONE_DIR"
npm install --save bpmn-js@latest diagram-js@latest
if [[ "x$SKIP_COMMIT" = "x" ]]; then
git config user.email "$BPMN_IO_EMAIL"
git config user.name "$BPMN_IO_USERNAME"
git config push.default simple
git add -A
git commit -m "deps: bump to bpmn-js@$TAG"
git tag "bpmn-js-$TAG"
git push -q &2>/dev/null
git push --tags -q &2>/dev/null
else
echo "Skipping commit (SKIP_COMMIT=$SKIP_COMMIT)"
fi
cd "$PWD"
================================================
FILE: tasks/stages/update-examples
================================================
#!/bin/bash
set -eo pipefail
shopt -s inherit_errexit nullglob
# update bpmn-js version in the project
PWD="$(pwd)"
WORKDIR="$(pwd)/tmp"
EXAMPLES_DIR="$WORKDIR/bpmn-js-examples"
# create work dir
mkdir -p "$WORKDIR"
git clone --depth=1 "https://$BPMN_IO_TOKEN@github.com/bpmn-io/bpmn-js-examples.git" "$EXAMPLES_DIR"
cd "$EXAMPLES_DIR"
TOOLKIT_VERSION="${TAG:1}"
echo "Updating toolkit version to $TOOLKIT_VERSION"
sed -i -E "s#(\"bpmn-js\": )\"[^\"]+\"#\1\"^$TOOLKIT_VERSION\"#" **/package.json
sed -i -E "s#/bpmn-js@[^/]+/#/bpmn-js@$TOOLKIT_VERSION/#" **/*.{html,md}
# install dependencies (fixes up lock file)
npm install
if [[ "x$SKIP_COMMIT" = "x" ]]; then
git config user.email "$BPMN_IO_EMAIL"
git config user.name "$BPMN_IO_USERNAME"
git config push.default simple
# add all resources
git add -A
git commit -m "deps: bump bpmn-js to $TAG"
git tag "$TAG"
git push -q &2>/dev/null
git push --tags -q &2>/dev/null
else
echo "Skipping commit (SKIP_COMMIT=$SKIP_COMMIT)"
fi
cd "$PWD"
================================================
FILE: tasks/stages/update-integration-test
================================================
#!/bin/bash
set -eo pipefail
shopt -s inherit_errexit nullglob
# bumps bpmn-js and diagram-js dependencies in bpmn-js-integration
PWD="$(pwd)"
WORKDIR="$(pwd)/tmp"
IT_DIR="$WORKDIR/bpmn-js-integration"
# create work dir
mkdir -p "$WORKDIR"
git clone --depth=1 "https://$BPMN_IO_TOKEN@github.com/bpmn-io/bpmn-js-integration.git" "$IT_DIR"
cd "$IT_DIR"
npm install --save bpmn-js@latest bpmn-moddle@latest
git config user.email "$BPMN_IO_EMAIL"
git config user.name "$BPMN_IO_USERNAME"
git config push.default simple
git add -A
git commit -m "deps: bump bpmn-js to $TAG"
git tag "$TAG"
git push -q &2>/dev/null
git push --tags -q &2>/dev/null
cd "$PWD"
================================================
FILE: tasks/stages/update-translations
================================================
#!/bin/bash
set -eo pipefail
shopt -s inherit_errexit nullglob
# updates translations and creates pull request
npm ci
npm run collect-translations
# exit if no changes
if [[ "x$(git status --porcelain docs/translations.json)" = "x" ]]; then echo "No changes; exiting" && exit 0; fi
if [[ "x$SKIP_COMMIT" = "x" ]]; then
git config user.email "$BPMN_IO_EMAIL"
git config user.name "$BPMN_IO_USERNAME"
git config push.default simple
BRANCH="update-translations-$(date +%Y%m%d%H%M%S)"
git switch -c $BRANCH
git add docs/translations.json
git commit -m "docs: update translations for $TAG"
git push -q --set-upstream origin $BRANCH
gh pr create --title "docs: update translations for $TAG" \
--body "This PR updates translations for $TAG" \
--reviewer "$REVIEWERS"
else
echo "Skipping commit (SKIP_COMMIT=$SKIP_COMMIT)"
fi
================================================
FILE: tasks/stages/update-website
================================================
#!/bin/bash
set -eo pipefail
shopt -s inherit_errexit nullglob
# update bpmn-js version in the project
PWD="$(pwd)"
WORKDIR="$(pwd)/tmp"
CLONE_DIR="$WORKDIR/bpmn.io"
# create work dir
mkdir -p "$WORKDIR"
git clone --depth=1 "https://$BPMN_IO_TOKEN@github.com/bpmn-io/bpmn.io.git" "$CLONE_DIR"
cd "$CLONE_DIR"
PUBLISHED=`date +"%F %H:%M"`
echo "Updating toolkit version to version=$TAG, published=$PUBLISHED on bpmn.io"
cat src/data/site.yml | \
tr "\r?\n" "\r" | \
sed -e "s#bpmnjs:\r version: [^\r]*\r published: [^\r]*\r#bpmnjs:\r version: $TAG\r published: $PUBLISHED\r#" | \
tr "\r" "\n" > src/data/site.yml_new
mv -f src/data/site.yml_new src/data/site.yml
if [[ "x$SKIP_COMMIT" = "x" ]]; then
git config user.email "$BPMN_IO_EMAIL"
git config user.name "$BPMN_IO_USERNAME"
git config push.default simple
# add all resources
git add -A
git commit -m "deps: bump bpmn-js to $TAG"
git push -q &2>/dev/null
else
echo "Skipping commit (SKIP_COMMIT=$SKIP_COMMIT)"
fi
cd "$PWD"
================================================
FILE: tasks/test-distro.mjs
================================================
import { execaSync as exec } from 'execa';
import assert from 'node:assert';
import fs from 'node:fs';
var failures = 0;
function runTest(variant, env) {
var NODE_ENV = process.env.NODE_ENV;
process.env.VARIANT = variant;
process.env.NODE_ENV = env;
console.log('[TEST] ' + variant + '@' + env);
console.log(`[EXEC] VARIANT=${variant} NODE_ENV=${env} karma start test/config/karma.distro.js`);
try {
exec('karma', [ 'start', 'test/config/karma.distro.js' ], {
stdio: 'inherit'
});
} catch (e) {
console.error('[TEST] FAILURE ' + variant + '@' + env);
console.error(e);
failures++;
} finally {
process.env.NODE_ENV = NODE_ENV;
}
}
function verifyAssets() {
const assets = [
'bpmn-font/css/bpmn-embedded.css',
'bpmn-font/font/bpmn.woff',
'bpmn-js.css',
'diagram-js.css'
];
for (const asset of assets) {
try {
assert.ok(fs.existsSync('dist/assets/' + asset), `${asset} missing`);
} catch (e) {
console.error('[TEST] ASSET ' + asset);
console.error(e);
failures++;
}
}
}
function test() {
runTest('bpmn-modeler', 'development');
runTest('bpmn-modeler', 'production');
runTest('bpmn-navigated-viewer', 'development');
runTest('bpmn-navigated-viewer', 'production');
runTest('bpmn-viewer', 'development');
runTest('bpmn-viewer', 'production');
verifyAssets();
if (failures) {
process.exit(1);
}
}
test();
================================================
FILE: test/TestHelper.js
================================================
export * from './helper';
import fileDrop from 'file-drops';
import {
insertCSS,
getBpmnJS
} from './helper';
insertCSS('diagram-js.css', require('diagram-js/assets/diagram-js.css'));
insertCSS('bpmn-js.css', require('../assets/bpmn-js.css'));
insertCSS('bpmn-embedded.css', require('bpmn-font/dist/css/bpmn-embedded.css'));
insertCSS('diagram-js-testing.css',
'body .test-container { height: auto }' +
'body .test-content-container { height: 90vh; }'
);
import ChaiMatch from 'chai-match';
import BoundsMatchers from './matchers/BoundsMatchers';
import ConnectionMatchers from './matchers/ConnectionMatchers';
import JSONMatcher from './matchers/JSONMatcher';
/* global chai */
// add suite specific matchers
chai.use(ChaiMatch);
chai.use(BoundsMatchers);
chai.use(ConnectionMatchers);
chai.use(JSONMatcher);
// be able to load files into running bpmn-js test cases
document.documentElement.addEventListener('dragover', fileDrop('Drop a BPMN diagram to open it in the currently active test.', function(files) {
const bpmnJS = getBpmnJS();
if (bpmnJS && files.length === 1) {
bpmnJS.importXML(files[0].contents);
}
}));
insertCSS('file-drops.css', `
.drop-overlay .box {
background: orange;
border-radius: 3px;
display: inline-block;
font-family: sans-serif;
padding: 4px 10px;
position: fixed;
top: 30px;
left: 50%;
transform: translateX(-50%);
}
`);
================================================
FILE: test/config/karma.distro.js
================================================
// configures browsers to run test against
// any of [ 'ChromeHeadless', 'Chrome', 'Firefox' ]
var browsers = (process.env.TEST_BROWSERS || 'ChromeHeadless').split(',');
// use puppeteer provided Chrome for testing
process.env.CHROME_BIN = require('puppeteer').executablePath();
var VARIANT = process.env.VARIANT;
var NODE_ENV = process.env.NODE_ENV;
module.exports = function(karma) {
karma.set({
basePath: '../../',
frameworks: [
'mocha',
'sinon-chai'
],
files: [
'dist/' + VARIANT + '.' + (NODE_ENV === 'production' ? 'production.min' : 'development') + '.js',
'dist/assets/bpmn-font/css/bpmn.css',
'dist/assets/diagram-js.css',
{ pattern: 'resources/initial.bpmn', included: false },
{ pattern: 'dist/assets/**/*', included: false },
'test/distro/helper.js',
'test/distro/' + VARIANT + '.js'
],
reporters: [ 'progress' ],
browsers,
browserNoActivityTimeout: 30000,
singleRun: true,
autoWatch: false
});
};
================================================
FILE: test/config/karma.unit.js
================================================
var path = require('path');
var fs = require('fs');
var collectTranslations = process.env.COLLECT_TRANSLATIONS;
var singleStart = process.env.SINGLE_START;
var coverage = process.env.COVERAGE;
// configures browsers to run test against
// any of [ 'ChromeHeadless', 'Chrome', 'Firefox', 'Safari' ]
var browsers = (process.env.TEST_BROWSERS || 'ChromeHeadless').split(',');
// use puppeteer provided Chrome for testing
process.env.CHROME_BIN = require('puppeteer').executablePath();
var tmpDir = path.join(__dirname, 'tmp');
fs.mkdirSync(tmpDir, { recursive: true });
var firefoxProfile = fs.mkdtempSync(path.join(tmpDir, 'firefox-profile'));
var basePath = '../..';
var absoluteBasePath = path.resolve(path.join(__dirname, basePath));
var suite = coverage ? 'test/coverageBundle.js' : 'test/testBundle.js';
module.exports = function(karma) {
var config = {
basePath,
frameworks: [
'mocha',
'sinon-chai',
'webpack'
],
files: [
suite
],
preprocessors: {
[ suite ]: [ 'webpack', 'env' ]
},
reporters: [ 'progress' ].concat(coverage ? 'coverage' : []),
customLaunchers: {
'FirefoxHeadless': {
base: 'Firefox',
flags: [ '-headless' ],
profile: firefoxProfile
}
},
coverageReporter: {
reporters: [
{ type: 'lcov', subdir: '.' }
]
},
envPreProcessor: [
'CI'
],
browsers,
browserNoActivityTimeout: 30000,
singleRun: true,
autoWatch: false,
webpack: {
mode: 'development',
module: {
rules: [
{
test: require.resolve('../TestHelper.js'),
sideEffects: true
},
{
test: /\.css|\.bpmn$/,
type: 'asset/source'
}
].concat(
coverage ? {
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
plugins: [
[ 'istanbul', {
include: [
'lib/**'
]
} ]
],
}
}
} : []
)
},
resolve: {
mainFields: [
'dev:module',
'module',
'main'
],
modules: [
'node_modules',
absoluteBasePath
]
},
devtool: 'eval-source-map'
}
};
if (collectTranslations) {
config.plugins = [].concat(config.plugins || [ 'karma-*' ], require('./translation-reporter'));
config.reporters = [].concat(config.reporters || [], 'translation-reporter');
config.envPreprocessor = [].concat(config.envPreprocessor || [], 'COLLECT_TRANSLATIONS');
}
if (singleStart) {
config.browsers = [].concat(config.browsers, 'Debug');
config.envPreprocessor = [].concat(config.envPreprocessor || [], 'SINGLE_START');
}
karma.set(config);
};
================================================
FILE: test/config/translation-reporter.js
================================================
var fs = require('fs');
var path = require('path');
var {
uniqueBy,
sortBy
} = require('min-dash');
function TranslationReporter() {
var translationsFile = path.join(__dirname, '../../docs/translations.json');
var translations = [];
this.onBrowserLog = function(browser, log, type) {
if (log === undefined || typeof log !== 'string') {
return;
}
if (log.substring(0, 1) === '\'') {
log = log.substring(1, log.length - 1);
}
try {
var obj = JSON.parse(log);
if (obj.type === 'translations') {
translations.push(obj.msg);
}
} catch (e) {
return;
}
};
this.onRunComplete = function() {
translations = uniqueBy(function(e) { return e; }, translations);
translations = sortBy(translations, function(e) { return e; });
fs.writeFileSync(translationsFile, JSON.stringify(translations, null, 2));
};
}
module.exports = {
'reporter:translation-reporter' : [ 'type', TranslationReporter ]
};
================================================
FILE: test/coverageBundle.js
================================================
var allTests = require.context('.', true, /(spec|integration).*Spec\.js$/);
allTests.keys().forEach(allTests);
var allSources = require.context('../lib', true, /.*\.js$/);
allSources.keys().forEach(allSources);
================================================
FILE: test/distro/bpmn-modeler.js
================================================
describe('bpmn-modeler', function() {
it('should expose globals', function() {
var BpmnJS = window.BpmnJS;
// then
expect(BpmnJS).to.exist;
expect(new BpmnJS()).to.exist;
});
it('should expose Viewer and NavigatedViewer', function() {
var BpmnJS = window.BpmnJS;
// then
expect(BpmnJS.NavigatedViewer).to.exist;
expect(new BpmnJS.NavigatedViewer()).to.exist;
expect(BpmnJS.Viewer).to.exist;
expect(new BpmnJS.Viewer()).to.exist;
});
it('should import initial diagram', function() {
var BpmnJS = window.BpmnJS;
// then
/* global testImport */
return testImport(BpmnJS);
});
});
================================================
FILE: test/distro/bpmn-navigated-viewer.js
================================================
describe('bpmn-navigated-viewer', function() {
it('should expose globals', function() {
var BpmnJS = window.BpmnJS;
// then
expect(BpmnJS).to.exist;
expect(new BpmnJS()).to.exist;
});
it('should expose Viewer', function() {
var BpmnJS = window.BpmnJS;
// then
expect(BpmnJS.Viewer).not.to.exist;
});
it('should import initial diagram', function() {
var BpmnJS = window.BpmnJS;
// then
/* global testImport */
return testImport(BpmnJS);
});
});
================================================
FILE: test/distro/bpmn-viewer.js
================================================
describe('bpmn-navigated-viewer', function() {
it('should expose globals', function() {
var BpmnJS = window.BpmnJS;
// then
expect(BpmnJS).to.exist;
expect(new BpmnJS()).to.exist;
});
it('should import initial diagram', function() {
var BpmnJS = window.BpmnJS;
// then
/* global testImport */
return testImport(BpmnJS);
});
});
================================================
FILE: test/distro/helper.js
================================================
async function testImport(BpmnJS, done) {
var container = document.createElement('div');
container.style.height = '500px';
container.style.border = 'solid 1px #666';
document.body.appendChild(container);
const response = await fetch('/base/resources/initial.bpmn');
if (!response.ok) {
throw new Error('failed to fetch diagram');
}
const diagramXML = await response.text();
var modeler = new BpmnJS({ container: container });
const { warnings } = await modeler.importXML(diagramXML);
if (warnings.length) {
throw new Error('imported with warnings');
}
return modeler;
}
window.testImport = testImport;
================================================
FILE: test/fixtures/bpmn/align-elements.bpmn
================================================
SequenceFlow_08zyuyv
SequenceFlow_08zyuyv
EndEvent_lane
SubProcess_lane
Task_lane
SequenceFlow_1nrce3c
SequenceFlow_0qa7db7
SequenceFlow_1nrce3c
SequenceFlow_0qa7db7
================================================
FILE: test/fixtures/bpmn/basic.bpmn
================================================
================================================
FILE: test/fixtures/bpmn/boundary-events.bpmn
================================================
SequenceFlow_1
SequenceFlow_1
================================================
FILE: test/fixtures/bpmn/collaboration/collaboration-data-store.bpmn
================================================
================================================
FILE: test/fixtures/bpmn/collaboration/collaboration-empty-participant.bpmn
================================================
================================================
FILE: test/fixtures/bpmn/collaboration/collaboration-message-flows.bpmn
================================================
SequenceFlow_1
SequenceFlow_2
SequenceFlow_1
SequenceFlow_2
================================================
FILE: test/fixtures/bpmn/collaboration/collaboration-participant.bpmn
================================================
SequenceFlow_1
SequenceFlow_2
SequenceFlow_1
SequenceFlow_2
================================================
FILE: test/fixtures/bpmn/collaboration/process-empty.bpmn
================================================
================================================
FILE: test/fixtures/bpmn/collaboration/process.bpmn
================================================
SequenceFlow_1
SequenceFlow_2
SequenceFlow_1
SequenceFlow_2
================================================
FILE: test/fixtures/bpmn/collaboration-data-items.bpmn
================================================
SubProcess_1
================================================
FILE: test/fixtures/bpmn/collaboration-message-flows.bpmn
================================================
================================================
FILE: test/fixtures/bpmn/collaboration-sequence-flows.bpmn
================================================
EndEvent_1
Task_1
ExclusiveGateway_1
SequenceFlow_1
SequenceFlow_2
SequenceFlow_1
SequenceFlow_2
================================================
FILE: test/fixtures/bpmn/collaboration-vertical.bpmn
================================================
Task_1
================================================
FILE: test/fixtures/bpmn/collaboration.bpmn
================================================
Task_1
================================================
FILE: test/fixtures/bpmn/collapsed-sub-process-legacy.bpmn
================================================
Flow_0obnxbt
Flow_1d6ajf7
Flow_0obnxbt
Flow_1d6ajf7
================================================
FILE: test/fixtures/bpmn/collapsed-sub-process.bpmn
================================================
sid-89A3F9F2-CCC8-46C7-816B-DD8AC8A98300
sid-89A3F9F2-CCC8-46C7-816B-DD8AC8A98300
sid-F06605E1-AEC1-4B39-8843-4AD3F547B557
sid-FC2ECAF5-771E-4ED3-BEF6-EFAB45E79500
sid-F06605E1-AEC1-4B39-8843-4AD3F547B557
sid-31F6EC44-E44C-4121-B4FE-BD69AF208C05
sid-EB275CF2-5EF1-44FA-B41B-71EB37CC2657
sid-EB275CF2-5EF1-44FA-B41B-71EB37CC2657
sid-FB543319-8DFB-4445-AAA3-720137FB230B
sid-FB543319-8DFB-4445-AAA3-720137FB230B
sid-B99D259B-1BD5-45FF-BD57-FB99C360BAC0
sid-472B540C-A0CD-46F4-9640-DF692EC1BFFC
sid-472B540C-A0CD-46F4-9640-DF692EC1BFFC
sid-910420B0-D11B-4F9D-B285-703D8AC0BA90
sid-A7460113-CB75-491D-817B-5E1A8C606B8C
sid-A7460113-CB75-491D-817B-5E1A8C606B8C
sid-01982395-64E8-43EF-A6D3-CDD276C312AA
sid-01982395-64E8-43EF-A6D3-CDD276C312AA
sid-910420B0-D11B-4F9D-B285-703D8AC0BA90
sid-B99D259B-1BD5-45FF-BD57-FB99C360BAC0
sid-FC2ECAF5-771E-4ED3-BEF6-EFAB45E79500
sid-5B23450F-AF5E-4519-B134-32107776BD44
sid-E71F5783-AFE7-44ED-8A9C-378C95087448
sid-E71F5783-AFE7-44ED-8A9C-378C95087448
sid-6B9741CD-D94B-41C7-A2EA-63A4C9445E16
sid-3BB6D6CA-BF45-4D15-A1AB-64686C41DB32
sid-3BB6D6CA-BF45-4D15-A1AB-64686C41DB32
sid-4E25B80E-EF68-4EE5-BB08-C1F54F1A7C39
sid-4E25B80E-EF68-4EE5-BB08-C1F54F1A7C39
sid-6B9741CD-D94B-41C7-A2EA-63A4C9445E16
sid-1A9DABC6-6079-4BF2-9D49-C4DC9569C519
sid-E5404926-738D-4447-87FE-FC6DD1E8BEFC
sid-E5404926-738D-4447-87FE-FC6DD1E8BEFC
sid-FED62A8F-6C3A-4BB2-8DE9-18FB0B35B50E
sid-FED62A8F-6C3A-4BB2-8DE9-18FB0B35B50E
sid-1A9DABC6-6079-4BF2-9D49-C4DC9569C519
sid-5B23450F-AF5E-4519-B134-32107776BD44
sid-31F6EC44-E44C-4121-B4FE-BD69AF208C05
sid-F7DA1903-6A1A-4858-AF4B-286A968C957F
sid-DCB98638-BEBD-4548-B501-F0E29AC71ED4
sid-DCB98638-BEBD-4548-B501-F0E29AC71ED4
sid-F7DA1903-6A1A-4858-AF4B-286A968C957F
sid-3FAE72F2-4037-4CBA-8B89-01D7FC7FF3E3
sid-3FAE72F2-4037-4CBA-8B89-01D7FC7FF3E3
================================================
FILE: test/fixtures/bpmn/complex.bpmn
================================================
sid-8E5D2B9D-6731-4FA7-BB27-C444A7236A69
sid-028EEE79-92D7-4C1B-B90D-905AA2697614
sid-7C1A234F-E066-438E-96E1-C0D7847288EA
sid-6EED0CFD-023A-4ACC-B944-42D90A617BDF
sid-CCECC1E8-CA9C-49BB-BEDF-75F173A11194
sid-F1FC14E4-8BBB-4647-9F9D-179663172496
sid-FF2BEA0D-55D5-4F2B-B7BA-195BC10CA9EB
sid-8910ED9B-DADC-4E11-9AD5-E12448B57ADF
sid-545B3227-D12A-43A8-B746-55E8C75F3A8A
sid-0FE7F936-F79B-4EB8-95F0-DC0AB97C682F
sid-05FFFE92-8A4C-43AB-8D9A-E14A1B0EE051
sid-28EA3990-9252-49F9-AD56-8FCDBFD7DA7D
sid-25203984-1D0A-494D-AD69-9140631D10FD
sid-6243D8FF-E57D-4D03-8234-40836D4E60D5
sid-72EBDBE9-3856-4AF7-9DDB-7C0F478E26D8
sid-57892D76-E413-4274-B8DE-FED72250C8A7
sid-6392228F-E287-40F3-9DD6-B91493F6B671
sid-EC21E3DA-12C5-4B74-83F7-530229F6C777
sid-A83B900A-A119-4FC4-A77F-09849C8660C9
sid-4A0A3787-3011-42F1-8CF7-16479922159E
sid-4A0A3787-3011-42F1-8CF7-16479922159E
sid-8F7A2A97-7C59-4B9E-AE03-625BB085C0E4
sid-028EEE79-92D7-4C1B-B90D-905AA2697614
sid-E8C87193-03FE-438D-A921-0BAB9FBD08D9
sid-8F7A2A97-7C59-4B9E-AE03-625BB085C0E4
sid-7E6EBD99-9B3A-44CE-972E-9CCDA7924AD8
sid-FD9AF1C4-E124-4C55-9B18-87804F8EC67E
sid-85F7873E-4458-4406-9D6E-1F4CA6268D55
sid-85F7873E-4458-4406-9D6E-1F4CA6268D55
sid-0D428B09-7C49-44AE-A257-7E810A541B0F
sid-F86867DE-BA70-47C1-8340-A6A5A3B645AA
sid-F86867DE-BA70-47C1-8340-A6A5A3B645AA
sid-DC611CCE-BA2C-459D-974D-4D09E2C390E6
sid-B9DF0BE4-4658-4908-8E4C-B528E8EA1FDD
sid-7BA05743-5D0C-4D1C-B193-22FE2A156E22
sid-DC611CCE-BA2C-459D-974D-4D09E2C390E6
sid-3C44D333-01F3-43B7-AE4F-F13DD6D05DAC
sid-B9DF0BE4-4658-4908-8E4C-B528E8EA1FDD
sid-F9A96365-936B-4461-8D51-C38EBA362A68
sid-F9A96365-936B-4461-8D51-C38EBA362A68
sid-9DE1A24F-5ED5-4095-B9C8-B7213895C7B7
sid-9DE1A24F-5ED5-4095-B9C8-B7213895C7B7
sid-950B6B3A-BEDA-49E3-A901-6733165E80C3
sid-950B6B3A-BEDA-49E3-A901-6733165E80C3
sid-3C44D333-01F3-43B7-AE4F-F13DD6D05DAC
sid-E54CB8B4-BED9-43AF-B03B-DBE60483A68A
sid-AEE9D356-E86D-4D7A-ABC5-AA6E76635A57
sid-C2A9F8A3-0E65-4BA2-A854-4B34EE2D2DFD
sid-C2A9F8A3-0E65-4BA2-A854-4B34EE2D2DFD
sid-7BA05743-5D0C-4D1C-B193-22FE2A156E22
sid-7E6EBD99-9B3A-44CE-972E-9CCDA7924AD8
sid-FD9AF1C4-E124-4C55-9B18-87804F8EC67E
sid-62177F1C-D8D7-488A-9F8A-E379831B4792
sid-A83B900A-A119-4FC4-A77F-09849C8660C9
sid-8BC0544C-1924-4422-B5BE-5CC1501312F4
sid-0D428B09-7C49-44AE-A257-7E810A541B0F
sid-62177F1C-D8D7-488A-9F8A-E379831B4792
sid-E54CB8B4-BED9-43AF-B03B-DBE60483A68A
sid-8BC0544C-1924-4422-B5BE-5CC1501312F4
sid-AEE9D356-E86D-4D7A-ABC5-AA6E76635A57
ID2
sid-B104C31F-A70F-4206-AF8E-442C5C2EEE49
sid-1F104A2B-C7CC-49E1-87F1-8391D31274BE
sid-AB73793C-D47A-4738-B34F-A82C6219A92C
sid-9838543B-F6B3-4432-A9B3-8B790A762147
sid-EA77B0E4-4512-4A05-B100-88605F5B7995
sid-2E9539B9-F0F1-4AEE-ADDB-4C8AD7A3920C
sid-4D91885E-63D7-460F-ACAB-3B1300D396FB
sid-C5457771-C93B-44FD-8D70-05849F25C775
sid-7C6C6457-0FF6-4074-BC87-D5653F7F8037
sid-C0A39E1E-6BA6-4A7C-98C4-597CEF56D803
sid-691FDEB6-F626-4AB1-8E90-3653C83C04EA
sid-DD0BC4E1-4AA3-4835-A477-373EA263A593
sid-D16273A3-B9E4-4D02-8072-3868DC29A662
sid-C01A5EDF-FDDA-4675-86DD-EC939CA503EA
sid-5C56D37A-6C15-43CB-8253-E56329A0F15B
sid-D453B3D6-0EA6-4607-A413-C6332ABE9F32
sid-07626FBD-C7FA-466D-AC08-F579B7A9C2EA
sid-17FF4D83-66C9-45AC-8B63-3A4BC45B94B3
sid-D833F570-90A1-46AB-B968-16751237C003
sid-D833F570-90A1-46AB-B968-16751237C003
sid-A2B3A2C7-61FA-40F5-9AF0-27FF0B6DE47D
sid-A2B3A2C7-61FA-40F5-9AF0-27FF0B6DE47D
sid-6AD87C60-225D-4BAB-8EAE-3AB8D0C31754
sid-B1598D75-E3CE-4CC5-8380-8FB570208B3B
sid-A2FA1E3C-8920-4F38-A2B1-BB75D9E33E85
sid-B1598D75-E3CE-4CC5-8380-8FB570208B3B
sid-8110675A-7E69-4B6B-95B9-9DE5DEF4BF32
sid-E1876DA2-53A4-4F8B-8392-20655993C733
sid-63119748-84AD-4A9B-8CDE-45B930B374B7
sid-63119748-84AD-4A9B-8CDE-45B930B374B7
sid-A2FA1E3C-8920-4F38-A2B1-BB75D9E33E85
sid-10D4CACF-4CA9-448C-93D1-BD9089C22BDE
sid-10D4CACF-4CA9-448C-93D1-BD9089C22BDE
sid-BD42B065-FCDE-4B2A-9107-3602645F43B1
sid-BD42B065-FCDE-4B2A-9107-3602645F43B1
sid-17B8A293-C347-4830-BFA1-E4941E9B120F
sid-6AD87C60-225D-4BAB-8EAE-3AB8D0C31754
sid-17B8A293-C347-4830-BFA1-E4941E9B120F
sid-73D82202-2D46-4210-9113-A2BE24C342C8
sid-17FF4D83-66C9-45AC-8B63-3A4BC45B94B3
sid-73D82202-2D46-4210-9113-A2BE24C342C8
sid-8110675A-7E69-4B6B-95B9-9DE5DEF4BF32
sid-E1876DA2-53A4-4F8B-8392-20655993C733
sid-FA4E05F6-ADB1-4F93-A0DA-3AEAEFB6D148
sid-E7730210-76BB-486F-BAB3-CA3994DF6AED
sid-FA4E05F6-ADB1-4F93-A0DA-3AEAEFB6D148
sid-713C88F2-5563-41E9-9351-F1FEEEBA72DA
sid-713C88F2-5563-41E9-9351-F1FEEEBA72DA
sid-E7730210-76BB-486F-BAB3-CA3994DF6AED
sid-2365FF07-4092-4B79-976A-AD192FE4E4E9
sid-2365FF07-4092-4B79-976A-AD192FE4E4E9
ID12
ID8,9
ID11
ID10
sid-991275D8-E60A-440A-B2DD-094D2B0049A4
sid-19BF350B-2D34-4049-B17C-AE0265F407CB
sid-D56D4E10-864F-4B2B-898E-AA9641C98E63
sid-0C2D523B-E4EB-4776-AFA3-43B156AAE378
sid-2D215BCD-C98A-4B3A-B255-EDCE32FF2A97
sid-2D215BCD-C98A-4B3A-B255-EDCE32FF2A97
sid-0B7D8255-893E-404A-B0EF-CCB418B98B58
sid-0B7D8255-893E-404A-B0EF-CCB418B98B58
sid-73C9EBCD-9625-4469-B4AD-87B829C4BD8B
sid-73C9EBCD-9625-4469-B4AD-87B829C4BD8B
sid-B7711D66-52EB-4437-AA0C-5671CE83C6E6
sid-066B769F-EAEB-42E5-ACAB-341240A5F87D
sid-4F2CDAD6-92D6-470B-B82C-8D07E050591A
sid-943A343F-C038-49B9-8640-7BDABFB8E1BF
sid-F88DDF30-3F4D-4FA6-AA82-3B10300FFE98
sid-F88DDF30-3F4D-4FA6-AA82-3B10300FFE98
sid-4E1F9DDA-0BDF-4BB0-90F5-E7A85C259444
sid-4E1F9DDA-0BDF-4BB0-90F5-E7A85C259444
sid-6E0CD175-BAC0-4104-8555-C2473AD6956B
sid-6E0CD175-BAC0-4104-8555-C2473AD6956B
sid-F5C79AC7-BB82-4CEC-A1E0-DB441A30CE08
sid-FB3673BF-5359-4ACE-B2A2-1C546E2D95C0
sid-2416E79A-ED80-4834-B7A9-A2F6F1B1F5D8
sid-DDA78FB8-B5BB-490F-8AB6-144A28AB646B
sid-8ED06068-AA04-428D-860D-8CA2A0483C2A
sid-AD5109AB-9014-4810-AB7B-3A4990FB44D1
sid-6B8D60D6-2BC0-4969-A16A-ABCD8586DEAD
sid-BA5E7744-B79B-47D2-8D85-7D38B00D52CF
sid-23B471A9-0A76-483C-AD0B-65092D0477BF
sid-CD80152E-8A60-4DF8-87F7-23BD0CDA3C58
sid-CD80152E-8A60-4DF8-87F7-23BD0CDA3C58
sid-FDB13333-B493-4477-9DE4-C7E4C522F495
sid-8006DF6D-4AC4-4BD4-8BE5-95CA3CCC182D
sid-FB3673BF-5359-4ACE-B2A2-1C546E2D95C0
sid-1B290110-0336-46CF-92EC-C45D43FA9307
sid-3E4A80E2-14C5-4002-BFE9-15F53566593B
sid-3E4A80E2-14C5-4002-BFE9-15F53566593B
sid-908C781B-BB04-488F-BA6F-07FCF03BDD32
sid-E5370FBF-0192-43B6-8288-43F5D7BE5854
sid-908C781B-BB04-488F-BA6F-07FCF03BDD32
sid-50B9F1DF-70D3-48DF-A96D-482225E11A7D
sid-50B9F1DF-70D3-48DF-A96D-482225E11A7D
sid-E5370FBF-0192-43B6-8288-43F5D7BE5854
sid-FDB13333-B493-4477-9DE4-C7E4C522F495
sid-1B290110-0336-46CF-92EC-C45D43FA9307
sid-1E8A77DE-E3F1-4DFA-B20C-9EEBF0735E05
sid-1E8A77DE-E3F1-4DFA-B20C-9EEBF0735E05
sid-699FBA56-8A57-43AC-ACC4-090A9B4AB26A
sid-1FFCF5B7-A6EE-4D63-AC49-5CD21F277383
sid-CC14630C-7FFB-4CD2-954A-1D52340F34B4
sid-8BC4F3B3-CA44-48C2-B3FA-137EAEC7D012
sid-F197542D-D274-45BE-95A5-7A3608BBE27A
sid-3820CBDB-C1E3-47EA-BCE1-10C9DBAEDB37
sid-308728F0-D1C5-4383-AA70-41249841A930
sid-4FA5730A-F51C-4CEE-98F0-631553512966
sid-7F1356BF-93F9-41C2-937C-E943B8818EB3
sid-47EBB822-715D-4193-8711-59063E3E4F48
sid-47EBB822-715D-4193-8711-59063E3E4F48
sid-B4BDF60C-F40F-4247-B52F-0EA0038A9039
sid-8006DF6D-4AC4-4BD4-8BE5-95CA3CCC182D
sid-1FFCF5B7-A6EE-4D63-AC49-5CD21F277383
sid-EF5AD02F-469C-45DB-AABA-29D5CDD54B58
sid-5C3AB4AB-5292-4EC3-9A0F-BAD26D56DBD4
sid-5C3AB4AB-5292-4EC3-9A0F-BAD26D56DBD4
sid-5C2E761C-74FF-405C-8F6E-416329D714BC
sid-E725BF0F-5B88-4D38-AE6A-7ABDA67CA7C7
sid-E725BF0F-5B88-4D38-AE6A-7ABDA67CA7C7
sid-5529AAF3-EC3E-409F-A020-8D0330A547E3
sid-5529AAF3-EC3E-409F-A020-8D0330A547E3
sid-5C2E761C-74FF-405C-8F6E-416329D714BC
sid-B4BDF60C-F40F-4247-B52F-0EA0038A9039
sid-F11A1E78-0DE1-46E5-9074-F665ED1985BE
sid-EF5AD02F-469C-45DB-AABA-29D5CDD54B58
sid-F11A1E78-0DE1-46E5-9074-F665ED1985BE
sid-A9859F1C-A85B-4F2F-B2DF-E3F4F7FA67FA
sid-B9B94A24-2819-461A-B5E8-61182BDA87DD
sid-86DC77E1-24A3-499E-89D0-499B4BBFF67A
sid-F0DA46E1-7E86-4236-9D49-290DD7B87C61
sid-BEC67F12-04E9-4AE2-B59E-FEB109CD3DF8
sid-303796CF-BA87-40A7-B2EC-9F6E10BD1060
sid-D3CA93A5-3BCC-4DB3-8075-EAC0C0A53991
sid-282D9841-8694-47FB-A058-32A5B47CFB1E
sid-597B2F0C-9759-421B-8F52-D8F26705F2BE
sid-6460C9E2-AD85-4EA6-8922-F9BAB25F1C5F
sid-F3E23BCC-F29B-444C-BF2B-C2AA7D9D0DEF
sid-770DE0CC-14BD-46C1-B93A-3C10BD8A3933
sid-E8C87193-03FE-438D-A921-0BAB9FBD08D9
sid-A9859F1C-A85B-4F2F-B2DF-E3F4F7FA67FA
sid-A9859F1C-A85B-4F2F-B2DF-E3F4F7FA67FA
sid-8D8BD39F-1B08-433F-8F93-A1FF7520BA8B
sid-770DE0CC-14BD-46C1-B93A-3C10BD8A3933
sid-155BB3AD-386C-4EF2-92C6-E2800D82A875
sid-3EB9F09F-9E44-4200-A845-09B78E5F6BA8
sid-F3E23BCC-F29B-444C-BF2B-C2AA7D9D0DEF
sid-3EB9F09F-9E44-4200-A845-09B78E5F6BA8
sid-BDC5800E-05A9-4D22-BA46-474DD7EBFF31
sid-8D8BD39F-1B08-433F-8F93-A1FF7520BA8B
sid-F0DA46E1-7E86-4236-9D49-290DD7B87C61
sid-F0DA46E1-7E86-4236-9D49-290DD7B87C61
sid-8D8BD39F-1B08-433F-8F93-A1FF7520BA8B
sid-155BB3AD-386C-4EF2-92C6-E2800D82A875
sid-BDC5800E-05A9-4D22-BA46-474DD7EBFF31
sid-F834923E-C5BB-44FF-9238-FE9B18B7ECD2
sid-F834923E-C5BB-44FF-9238-FE9B18B7ECD2
sid-EA96E73A-B3ED-48A6-B066-5B38CA75DBE6
sid-EA96E73A-B3ED-48A6-B066-5B38CA75DBE6
sid-1D3A1400-EBE2-4C67-B377-CC31EB9925E5
sid-1D3A1400-EBE2-4C67-B377-CC31EB9925E5
sid-54D77E49-FBFB-4B81-932A-B8F5E3BE4C6A
sid-54D77E49-FBFB-4B81-932A-B8F5E3BE4C6A
Frist = 1 Monate vor Besetzung der Stelle
ID1
ID13
sid-36E152C5-1864-4D96-9F9B-27133FD47EFE
sid-B1C30549-F180-4515-9926-F2036892B4C1
sid-E91A2C66-8518-4301-8912-DA783975DD45
sid-369E18F1-20E3-4E09-A8B7-1FF568E57F23
sid-369E18F1-20E3-4E09-A8B7-1FF568E57F23
sid-D654E8D0-0A05-48F1-B102-B547E61DDDB6
sid-90EB4714-6D5C-48A0-87BB-BE2024FE22BD
sid-8BB76718-C32D-4E09-B80B-7DC168E99147
sid-8BB76718-C32D-4E09-B80B-7DC168E99147
sid-48AAE1F8-75D9-4031-ACAD-49BD8A16E9C8
sid-90EB4714-6D5C-48A0-87BB-BE2024FE22BD
sid-FE1C5021-4268-4AE6-8FBD-9DF197259520
sid-97FBBC72-9B16-470C-BAF3-445654369DF9
sid-1203CB8F-6985-4231-B352-DE313ECA48CE
sid-1203CB8F-6985-4231-B352-DE313ECA48CE
sid-B8E5BEAC-DBF0-45D3-A8A5-1D0D1EF0E19B
sid-97FBBC72-9B16-470C-BAF3-445654369DF9
sid-7AD16339-3A08-4841-96D9-3164E76DCF8F
sid-7AD16339-3A08-4841-96D9-3164E76DCF8F
sid-4FA0508C-2BC1-4ABB-8F75-ED27215EE73D
sid-B8E5BEAC-DBF0-45D3-A8A5-1D0D1EF0E19B
sid-C642640E-F524-40F0-BD91-41961D14ED31
sid-C642640E-F524-40F0-BD91-41961D14ED31
sid-5F689961-FD7E-4383-8ECE-B6946007D211
sid-258E87AC-23E8-4C39-92C8-5AE7DE19992A
sid-4FA0508C-2BC1-4ABB-8F75-ED27215EE73D
sid-7E2E4777-EC3D-4484-8606-A1ACB6FE84BE
sid-373FAB4D-A45B-45EC-9F85-B1076ABBFBA6
sid-5F689961-FD7E-4383-8ECE-B6946007D211
sid-04E0977B-6883-4B93-AD20-5A2F8B496DE6
sid-7E2E4777-EC3D-4484-8606-A1ACB6FE84BE
sid-9B1BDCA4-28B7-426E-A13C-66409FAC588B
sid-04E0977B-6883-4B93-AD20-5A2F8B496DE6
sid-9FB7EAEB-5A8B-4D90-B3A7-6B52FCBD5D22
sid-97D1C665-6495-46A4-BA35-8961F07A076F
sid-9FB7EAEB-5A8B-4D90-B3A7-6B52FCBD5D22
sid-4D49AA03-7D9E-4086-A0D9-43E7C741DCD3
sid-F52F82C6-C6C5-4E39-A2A9-7F4F780748A5
sid-9E3DEB10-BFD8-4D7D-80EC-F98CD2E65DA4
sid-9E3DEB10-BFD8-4D7D-80EC-F98CD2E65DA4
sid-4D49AA03-7D9E-4086-A0D9-43E7C741DCD3
sid-9B1BDCA4-28B7-426E-A13C-66409FAC588B
sid-48AAE1F8-75D9-4031-ACAD-49BD8A16E9C8
sid-373FAB4D-A45B-45EC-9F85-B1076ABBFBA6
sid-FE1C5021-4268-4AE6-8FBD-9DF197259520
sid-97D1C665-6495-46A4-BA35-8961F07A076F
sid-258E87AC-23E8-4C39-92C8-5AE7DE19992A
sid-F52F82C6-C6C5-4E39-A2A9-7F4F780748A5
ID3
ID4
sid-D654E8D0-0A05-48F1-B102-B547E61DDDB6
sid-155B51B2-12E4-4A0B-AFDF-DDBA2EE093D5
sid-3B833F36-ABCE-49A9-B268-445BAC9F758D
sid-A56AC510-264D-4291-B1CE-A035C2037437
sid-9A0A93AB-0A05-4D01-BF02-14A9C3189E84
sid-8CE3896D-0181-49CE-A1A9-C085262DA0FA
sid-42305309-4E98-47F0-80C4-690E2DB222C0
sid-4C1265A5-A535-48FB-99E9-68E0EC37C624
sid-53EE6F0D-43BE-4883-B018-3BB90743DC25
sid-91FF7684-71E6-497E-9938-0C9469B597EA
sid-286F337E-9361-4B4D-AF78-7E403CA80DA1
sid-DC5EFA1A-9841-4010-A16B-D771AA2B57C3
sid-8700D448-747C-4559-9A57-BFE8AAD639D4
sid-DB5144E8-CEDC-4333-BE75-A63907FDC5F0
sid-3906E383-5898-47F6-84FE-CBA12360BCF9
sid-11A2840A-596D-4937-BB37-0D5952E03535
sid-3906E383-5898-47F6-84FE-CBA12360BCF9
FOO
sid-D3213E0A-906E-4276-8DAF-208E4F416D51
sid-CBB13D0A-5D67-4908-9D56-4E1A1E2D0E71
sid-11A2840A-596D-4937-BB37-0D5952E03535
sid-D3213E0A-906E-4276-8DAF-208E4F416D51
sid-8E8DC42E-9AD4-4D6F-AEFC-C6FE6E91B6D3
sid-8E8DC42E-9AD4-4D6F-AEFC-C6FE6E91B6D3
sid-D3E0C8B2-1DEC-4283-9FB3-977DB4382650
sid-D3E0C8B2-1DEC-4283-9FB3-977DB4382650
sid-1CA535D5-1E51-41B7-A555-3E8BB651FE97
sid-CBB13D0A-5D67-4908-9D56-4E1A1E2D0E71
sid-1CA535D5-1E51-41B7-A555-3E8BB651FE97
sid-47FE04AD-8529-45E5-87E6-F7992404494A
sid-47FE04AD-8529-45E5-87E6-F7992404494A
sid-778CB5F7-7B50-4086-AEAF-A84CA5D34A8F
sid-778CB5F7-7B50-4086-AEAF-A84CA5D34A8F
sid-CB506E96-0C3C-4B65-9290-18EA192E62FE
sid-67782ED7-78C0-47F3-9EAC-46F9F194D843
sid-CB506E96-0C3C-4B65-9290-18EA192E62FE
sid-7363B1EE-93CD-49D4-966B-8F1368A01010
sid-7363B1EE-93CD-49D4-966B-8F1368A01010
sid-67782ED7-78C0-47F3-9EAC-46F9F194D843
sid-ADF333C6-9197-4556-A2C4-19BBC7122609
sid-ADF333C6-9197-4556-A2C4-19BBC7122609
ID7
ID5
ID6
ID1: Was passiert wenn sich kein Bewerber gemeldet hat? --> Anpassen der Stellenausschreibung, löschen dieser?
ID2: Die Bewerbung soll auch archiviert werden, jedoch mit Status "bei Eingang abgelehnt"
ID3: Die Informationen wann welcher Bewerber eingeladen wurde, soll bitte im Tool erfasst werden.
ID4: Müssen hier die Bewerbungs-unterlagen zurückgesendet werden?
ID5: Müssen hier die Bewerbungsunterlagen zurückgesendet werden?
ID6: kann der Bewerber auch "geparkt" werden, wenn weitere Gespräche folgen? wenn ja, wie soll das geschehen?
ID7: Was passiert wenn der Teilnehmer nicht erscheint?
ID8: Müssen hier die Bewerbungsunterlagen zurückgesendet werden?
ID9: Wie laufen jetzt alle anderen Bewerbungsgespräche? Werden die neu gestartet?
ID10: Muss jetzt die Stelle offline genommen werden?
ID11: Wie funktioniert das genau? Prozesskopplung zu Management Bewerbungsgespräche?
ID12: Soll IT-unterstützt laufen, gemäß vordefiniertem Template
ID13: Vorgabe wie das Filtern von statten gehen soll?! --> Business Rules
Annahmen:
- alle Bewerbungen gehen über Personal ein.
- unvollständige Bewerbungen werden nicht verfolgt bzw. nach einer Frist verworfen
- Es wurden nur Bewerber eingeladen, da aus Personalsicht inhaltlich geprüft sind. (z.B. müssen die Bewerbungen vollständig sein!)
Fehlt noch:
- Teilnehmer kann rückmelden, dass er Bewerbung zurückzieht
- Teilnehmer kann rückmelden, dass er Bewerbungsgespräch nicht wahrnehmen kann (komplette Absage oder neuen Termin vereinbaren)
- Verwendung von Daten ist nicht konsistent im Modell eingezeichnet
- Klärung der offenen ToDos direkt im Diagramm
================================================
FILE: test/fixtures/bpmn/conditions.bpmn
================================================
SequenceFlow_1
SequenceFlow_3
SequenceFlow_4
bar}]]>
SequenceFlow_2
SequenceFlow_4
SequenceFlow_1
SequenceFlow_2
SequenceFlow_3
================================================
FILE: test/fixtures/bpmn/containers.bpmn
================================================
SubProcess_1
Transaction_1
SubProcess_2
IntermediateThrowEvent_4
IntermediateCatchEvent_2
================================================
FILE: test/fixtures/bpmn/distribute-elements-filtering.bpmn
================================================
SequenceFlow_0vrvkcp
SequenceFlow_0vrvkcp
SequenceFlow_1jet52k
SequenceFlow_1jet52k
================================================
FILE: test/fixtures/bpmn/distribute-elements-filtering.collaboration.bpmn
================================================
================================================
FILE: test/fixtures/bpmn/distribute-elements.bpmn
================================================
StartEvent_1
Task_1b6t3j8
ExclusiveGateway_10cec0a
Task_08pns8h
Task_0511uak
EndEvent_0c9irey
Task_0s70clu
Task_0uun3ot
EndEvent_0uru64y
SequenceFlow_1yhjvt1
SequenceFlow_1yhjvt1
SequenceFlow_0dnhiaa
DataObjectReference_0sy638s
SequenceFlow_00cqlm8
SequenceFlow_1r5jt4v
SequenceFlow_1elxpyw
SequenceFlow_1r5jt4v
SequenceFlow_17fos5h
SequenceFlow_1elxpyw
SequenceFlow_1wgufjm
DataStoreReference_0mi9txb
SequenceFlow_0dt45uz
SequenceFlow_1wgufjm
SequenceFlow_0dt45uz
DataStoreReference_0mi9txb
Property_1qb88ea
SequenceFlow_0dnhiaa
SequenceFlow_00cqlm8
DataObjectReference_0sy638s
Property_17vcvoy
SequenceFlow_17fos5h
================================================
FILE: test/fixtures/bpmn/draw/activity-markers-combination.bpmn
================================================
================================================
FILE: test/fixtures/bpmn/draw/activity-markers-simple.bpmn
================================================
foo
bar
foo
bar
================================================
FILE: test/fixtures/bpmn/draw/activity-markers.bpmn
================================================
================================================
FILE: test/fixtures/bpmn/draw/associations.bpmn
================================================
Association_None
Association_One
Association_Both
Association_None
Association_One
Association_Both
================================================
FILE: test/fixtures/bpmn/draw/boundary-event-with-refnode.bpmn
================================================
_d58753a7-d38b-49cd-914d-14e4cdaa4449
End_Event
Boundary_Event
sid-4F795442-BADE-4AD2-AC9A-C51F90503931
sid-4F795442-BADE-4AD2-AC9A-C51F90503931
================================================
FILE: test/fixtures/bpmn/draw/boundary-event-without-refnode.bpmn
================================================
_d58753a7-d38b-49cd-914d-14e4cdaa4449
End_Event
sid-4F795442-BADE-4AD2-AC9A-C51F90503931
sid-4F795442-BADE-4AD2-AC9A-C51F90503931
================================================
FILE: test/fixtures/bpmn/draw/boundary-event-z-index.bpmn
================================================
================================================
FILE: test/fixtures/bpmn/draw/call-activity.bpmn
================================================
================================================
FILE: test/fixtures/bpmn/draw/conditional-flow-default.bpmn
================================================
sid-F71776EB-FC77-47EC-A608-4D90422C9C9D
sid-F71776EB-FC77-47EC-A608-4D90422C9C9D
sid-8372F73C-BF60-4F06-A520-F3D010C17849
sid-586C231A-A870-475A-9CC7-6E5EB7D96EA0
sid-8372F73C-BF60-4F06-A520-F3D010C17849
sid-2AD76A90-5C48-48D5-A1A4-B965E9A176F1
sid-2AD76A90-5C48-48D5-A1A4-B965E9A176F1
sid-3D50F08F-F5CA-41F5-8B2D-8AC1E3EE12C1
sid-F24B422A-FC22-444F-9B47-B6220BC8BC79
sid-3D50F08F-F5CA-41F5-8B2D-8AC1E3EE12C1
sid-F861C2E1-3A74-4EE0-850B-ED7251335F00
sid-F861C2E1-3A74-4EE0-850B-ED7251335F00
sid-586C231A-A870-475A-9CC7-6E5EB7D96EA0
sid-F24B422A-FC22-444F-9B47-B6220BC8BC79
================================================
FILE: test/fixtures/bpmn/draw/conditional-flow-gateways.bpmn
================================================
SequenceFlow_1
SequenceFlow_1
SequenceFlow_2
SequenceFlow_3
test
SequenceFlow_2
SequenceFlow_3
================================================
FILE: test/fixtures/bpmn/draw/conditional-flow-typed-task.bpmn
================================================
SequenceFlow_1
FOO
SequenceFlow_1
================================================
FILE: test/fixtures/bpmn/draw/conditional-flow.bpmn
================================================
sid-3FA58AD7-6491-4C89-A636-42E47488D426
sid-82C30D2C-10BC-4035-8A14-B50298F120E9
sid-A35FA3B7-318C-49FE-8446-70B6EC8329A5
sid-A35FA3B7-318C-49FE-8446-70B6EC8329A5
sid-3FA58AD7-6491-4C89-A636-42E47488D426
sid-82C30D2C-10BC-4035-8A14-B50298F120E9
sid-262FECFE-432B-42B6-AAF7-040A6B6D1880
sid-262FECFE-432B-42B6-AAF7-040A6B6D1880
sid-F35A9A61-6B46-4F0F-8885-FC17ED3D3CBB
sid-314B00E4-B24D-485E-B645-5D692C5AD9DE
sid-314B00E4-B24D-485E-B645-5D692C5AD9DE
sid-AF879D05-477F-45C5-A8CB-C3C5FC1F70E9
sid-F35A9A61-6B46-4F0F-8885-FC17ED3D3CBB
sid-54D7C967-8E1B-4282-82C4-E3105B6AC0AC
sid-AF879D05-477F-45C5-A8CB-C3C5FC1F70E9
sid-54D7C967-8E1B-4282-82C4-E3105B6AC0AC
sid-3A48A6D4-00FD-453B-89CE-A453D68C2732
sid-52DA4A1B-A188-4866-AD39-ED24F6BDF9D0
sid-19AA87BE-9229-4063-83A9-AC10C98C1D67
sid-AA6359EA-B251-41E9-8A80-5870C35203FB
sid-05966EA5-FA89-4F41-B59E-BEF1F717B05F
sid-1C6223C2-572C-4B53-82B3-A7498A046B3E
sid-DB9390D1-45D2-4A16-BEA3-D14D8715E057
sid-3A48A6D4-00FD-453B-89CE-A453D68C2732
sid-52DA4A1B-A188-4866-AD39-ED24F6BDF9D0
sid-AA6359EA-B251-41E9-8A80-5870C35203FB
sid-1C6223C2-572C-4B53-82B3-A7498A046B3E
sid-19AA87BE-9229-4063-83A9-AC10C98C1D67
sid-05966EA5-FA89-4F41-B59E-BEF1F717B05F
sid-DB9390D1-45D2-4A16-BEA3-D14D8715E057
sid-BBFAEEF1-BEFC-43DD-B045-4D7B662868A7
sid-BBFAEEF1-BEFC-43DD-B045-4D7B662868A7
#{selectedFlow=='A'}
#{selectedFlow=='B'}
================================================
FILE: test/fixtures/bpmn/draw/data-objects.bpmn
================================================
DataInput_1
sid-ADF95ACC-DEA6-4F6F-AF91-8F5BABB299AF
DataOutput_1
sid-16646BD1-38D3-499A-95A6-42A75D8D2510
sid-b4a08214-9dd4-40ad-8f82-4620ee384231
sid-6c061a84-a3f2-4b69-93a5-8a3cafa27437
sid-6c061a84-a3f2-4b69-93a5-8a3cafa27437
sid-b4a589fa-6809-485d-ac79-9906084551f5
sid-d5968987-50e5-4e42-9a3e-e8e3f6cc53bc
sid-bff296e6-46b4-4c63-9def-f68cdf80510d
sid-bff296e6-46b4-4c63-9def-f68cdf80510d
DataObjectReference_1
sid-b4a08214-9dd4-40ad-8f82-4620ee384231
sid-ADF95ACC-DEA6-4F6F-AF91-8F5BABB299AF
_DataStoreReference_2
sid-b4a589fa-6809-485d-ac79-9906084551f5
DataOutput_1
sid-d5968987-50e5-4e42-9a3e-e8e3f6cc53bc
DataObjectReference_1
sid-16646BD1-38D3-499A-95A6-42A75D8D2510
_DataStoreReference_2
================================================
FILE: test/fixtures/bpmn/draw/event-subprocess-icons.bpmn
================================================
Flow_01
Flow_01
Flow_02
Flow_02
PT30S
Flow_03
Flow_03
Flow_04
Flow_04
Flow_05
Flow_05
Flow_06
Flow_06
Flow_07
Flow_07
Flow_08
Flow_08
Flow_09
Flow_09
Flow_11
Flow_11
Flow_12
Flow_12
Flow_13
Flow_13
Flow_14
Flow_14
Flow_15
Flow_15
Flow_16
Flow_16
Flow_17
Flow_17
Flow_18
Flow_18
Flow_19
Flow_19
Flow_31
Flow_31
Flow_32
Flow_32
Flow_33
Flow_33
Flow_34
Flow_34
Flow_35
Flow_35
Flow_38
Flow_38
Flow_39
Flow_39
Flow_41
Flow_41
Flow_42
Flow_42
Flow_43
Flow_43
Flow_44
Flow_44
Flow_45
Flow_45
Flow_48
Flow_48
Flow_49
Flow_49
================================================
FILE: test/fixtures/bpmn/draw/event-subprocesses-collapsed.bpmn
================================================
No Non-Interrupting event
No Non-Interrupting event
================================================
FILE: test/fixtures/bpmn/draw/event-subprocesses-expanded.bpmn
================================================
No Non-Interrupting
No Non-Interrupting
================================================
FILE: test/fixtures/bpmn/draw/events-interrupting.bpmn
================================================
================================================
FILE: test/fixtures/bpmn/draw/events.bpmn
================================================
================================================
FILE: test/fixtures/bpmn/draw/gateway-type-default.bpmn
================================================
Should rendered as event based Gateway
================================================
FILE: test/fixtures/bpmn/draw/gateways.bpmn
================================================
================================================
FILE: test/fixtures/bpmn/draw/group-name.bpmn
================================================
================================================
FILE: test/fixtures/bpmn/draw/group.bpmn
================================================
================================================
FILE: test/fixtures/bpmn/draw/message-label.bpmn
================================================
================================================
FILE: test/fixtures/bpmn/draw/message-marker.bpmn
================================================
Non Initiating
Normal message flow
================================================
FILE: test/fixtures/bpmn/draw/pools-with-collection-marker.bpmn
================================================
================================================
FILE: test/fixtures/bpmn/draw/pools.bpmn
================================================
================================================
FILE: test/fixtures/bpmn/draw/task-types.bpmn
================================================
================================================
FILE: test/fixtures/bpmn/draw/text-annotation.bpmn
================================================
Test test consetetursadipscingelitrametamet nonumy
Lorem ipsum dolor sit amet, consetetursadipscingelitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor s
================================================
FILE: test/fixtures/bpmn/draw/vertical-pools.bpmn
================================================
================================================
FILE: test/fixtures/bpmn/draw/xor.bpmn
================================================
Should be blank
================================================
FILE: test/fixtures/bpmn/empty-definitions.bpmn
================================================
================================================
FILE: test/fixtures/bpmn/error/categoryValue.bpmn
================================================
================================================
FILE: test/fixtures/bpmn/error/di-plane-no-bpmn-element.bpmn
================================================
================================================
FILE: test/fixtures/bpmn/error/duplicate-ids.bpmn
================================================
================================================
FILE: test/fixtures/bpmn/error/invalid-child.bpmn
================================================
================================================
FILE: test/fixtures/bpmn/error/missing-namespace.bpmn
================================================
================================================
FILE: test/fixtures/bpmn/error/no-process-collaboration.bpmn
================================================
================================================
FILE: test/fixtures/bpmn/error/no-xml.txt
================================================
this is no xml
================================================
FILE: test/fixtures/bpmn/event-sub-processes.bpmn
================================================
================================================
FILE: test/fixtures/bpmn/extension/camunda.bpmn
================================================
SequenceFlow_1
stringInListNestedInMap
${ 'b' }
stringConstantValue
SequenceFlow_1
SequenceFlow_2
SequenceFlow_2
================================================
FILE: test/fixtures/bpmn/extension/custom-override.bpmn
================================================
SequenceFlow_1
stringInListNestedInMap
${ 'b' }
stringConstantValue
SequenceFlow_1
SequenceFlow_2
SequenceFlow_2
================================================
FILE: test/fixtures/bpmn/extension/custom.bpmn
================================================
SequenceFlow_1
stringInListNestedInMap
${ 'b' }
stringConstantValue
SequenceFlow_1
SequenceFlow_2
SequenceFlow_2
================================================
FILE: test/fixtures/bpmn/features/drop/drop.bpmn
================================================
ID_Sequenceflow_1
ID_Sequenceflow_1
================================================
FILE: test/fixtures/bpmn/features/drop/recursive-task.bpmn
================================================
ID_sequenceflow_1
================================================
FILE: test/fixtures/bpmn/features/replace/01_replace.bpmn
================================================
SequenceFlow_1
SequenceFlow_1
SequenceFlow_2
SequenceFlow_2
SequenceFlow_3
SequenceFlow_3
SequenceFlow_8
SequenceFlow_7
SequenceFlow_6
SequenceFlow_4
SequenceFlow_4
SequenceFlow_5
SequenceFlow_5
SequenceFlow_6
SequenceFlow_8
P1D
SequenceFlow_7
${a < b}
================================================
FILE: test/fixtures/bpmn/features/replace/association-gateways.bpmn
================================================
SequenceFlow_0fn1a6r
SequenceFlow_0fn1a6r
SequenceFlow_19u6x8u
SequenceFlow_0agwpbc
SequenceFlow_1p61uf0
SequenceFlow_0agwpbc
SequenceFlow_19u6x8u
SequenceFlow_1p61uf0
SequenceFlow_0608fzs
SequenceFlow_1rme11l
SequenceFlow_0608fzs
SequenceFlow_1rme11l
================================================
FILE: test/fixtures/bpmn/features/replace/cancel-events.bpmn
================================================
================================================
FILE: test/fixtures/bpmn/features/replace/connection.bpmn
================================================
SequenceFlow_1
SequenceFlow_1
================================================
FILE: test/fixtures/bpmn/features/replace/copy-properties.bpmn
================================================
hello world
foo
bar
10
SequenceFlow_1e74z8m
SequenceFlow_1tdxph9
DataStoreReference_1elrt45
Property_0j0o7pl
DataObjectReference_0hkbt95
Property_0j0o7pl
DataStoreReference_1j8ymac
DataObjectReference_1js94kb
SequenceFlow_1e74z8m
SequenceFlow_1tdxph9
================================================
FILE: test/fixtures/bpmn/features/replace/data-elements.bpmn
================================================
================================================
FILE: test/fixtures/bpmn/features/replace/data-stores-positioned-against-participant.bpmn
================================================
================================================
FILE: test/fixtures/bpmn/features/replace/participants.bpmn
================================================
================================================
FILE: test/fixtures/bpmn/features/rules/event-based-gateway-outgoing-edge.bpmn
================================================
================================================
FILE: test/fixtures/bpmn/features/rules/link-event.bpmn
================================================
================================================
FILE: test/fixtures/bpmn/features/rules/text-annotation-association.bpmn
================================================
A label
================================================
FILE: test/fixtures/bpmn/flow-markers.bpmn
================================================
sid-37185958-C044-425D-9B8C-DB8DEE274097
sid-B681960F-8C8E-4E3E-8310-0DF3126FD429
sid-5616CC3E-C211-4BE0-A0FB-DED35046FA90
sid-4E41E3B4-66AB-492E-AB51-C6499800D240
sid-69CE7F49-8ECD-45B6-9BBD-327796B854CE
sid-7282B370-204F-44BE-AACA-B33047993F81
sid-d548519b-5784-4e5b-9be9-7a9e082ae9b4
sid-d548519b-5784-4e5b-9be9-7a9e082ae9b4
sid-33abed7e-c0d1-4ccb-a95a-ae5e61faba30
sid-fd64d367-a3a9-4546-b3a7-983b5e2c517f
sid-fd64d367-a3a9-4546-b3a7-983b5e2c517f
sid-2FB8A264-C197-4769-A020-02F3654875A0
sid-33abed7e-c0d1-4ccb-a95a-ae5e61faba30
sid-F5232AB9-7F31-43EE-B0EC-A74EB8CD96DE
sid-69CE7F49-8ECD-45B6-9BBD-327796B854CE
sid-6DCC22DB-1B44-4DE7-A9F5-60C2070B73CC
sid-CC60949F-0A88-4902-9011-0FE6AA355254
sid-6DCC22DB-1B44-4DE7-A9F5-60C2070B73CC
sid-7282B370-204F-44BE-AACA-B33047993F81
sid-CC60949F-0A88-4902-9011-0FE6AA355254
sid-d17816af-06d3-4ecd-b051-7b9d4c6ddcc2
sid-8f07615b-9240-4b27-b38e-a76869b913dc
sid-8f07615b-9240-4b27-b38e-a76869b913dc
sid-9b67a3af-0e23-4f34-9f75-20cd544c9bad
sid-9b67a3af-0e23-4f34-9f75-20cd544c9bad
sid-F5232AB9-7F31-43EE-B0EC-A74EB8CD96DE
sid-d17816af-06d3-4ecd-b051-7b9d4c6ddcc2
foo
foo
A gateway
A pool
================================================
FILE: test/fixtures/bpmn/import/boundaryEvent.bpmn
================================================
SequenceFlow_1
SequenceFlow_1
================================================
FILE: test/fixtures/bpmn/import/collaboration.bpmn
================================================
Task_1
================================================
FILE: test/fixtures/bpmn/import/collapsed/collaboration.bpmn
================================================
SubProcess_2
SubProcess_1
================================================
FILE: test/fixtures/bpmn/import/collapsed/process.bpmn
================================================
================================================
FILE: test/fixtures/bpmn/import/collapsed/processWithChildren.bpmn
================================================
SequenceFlow_1
SequenceFlow_4
SequenceFlow_2
SequenceFlow_2
SequenceFlow_5
SequenceFlow_6
SequenceFlow_6
SequenceFlow_7
SequenceFlow_5
SequenceFlow_3
SequenceFlow_3
SequenceFlow_7
SequenceFlow_1
SequenceFlow_4
SequenceFlow_9
SequenceFlow_8
SequenceFlow_9
SequenceFlow_8
SequenceFlow_10
SequenceFlow_10
SequenceFlow_11
SequenceFlow_11
================================================
FILE: test/fixtures/bpmn/import/collapsed-subprocess.bpmn
================================================
================================================
FILE: test/fixtures/bpmn/import/data-store.inside-participant.bpmn
================================================
================================================
FILE: test/fixtures/bpmn/import/data-store.outside-participant.dangling.bpmn
================================================
================================================
FILE: test/fixtures/bpmn/import/data-store.outside-participant.participant.bpmn
================================================
================================================
FILE: test/fixtures/bpmn/import/data-store.outside-participant.subprocess.bpmn
================================================
================================================
FILE: test/fixtures/bpmn/import/default-attrs.bpmn
================================================
================================================
FILE: test/fixtures/bpmn/import/error/boundaryEvent-invalidAttachToRef.bpmn
================================================
SequenceFlow_1
SequenceFlow_1
================================================
FILE: test/fixtures/bpmn/import/error/boundaryEvent-missingAttachToRef.bpmn
================================================
SequenceFlow_1
SequenceFlow_1
================================================
FILE: test/fixtures/bpmn/import/error/dangling-process-message-flow.bpmn
================================================
_1c347d0d-750b-4c09-980d-6877caae409b
_6fed62c8-8241-4a1d-ae67-266fda7dcead
================================================
FILE: test/fixtures/bpmn/import/error/invalid-flow-element.bpmn
================================================
Task_1
TextAnnotation_1
================================================
FILE: test/fixtures/bpmn/import/error/multiple-dis.bpmn
================================================
================================================
FILE: test/fixtures/bpmn/import/groups.bpmn
================================================
================================================
FILE: test/fixtures/bpmn/import/labels/collaboration-message-flows.bpmn
================================================
================================================
FILE: test/fixtures/bpmn/import/labels/collaboration.bpmn
================================================
================================================
FILE: test/fixtures/bpmn/import/labels/embedded.bpmn
================================================
================================================
FILE: test/fixtures/bpmn/import/labels/external-no-di.bpmn
================================================
SequenceFlow_1
SequenceFlow_1
================================================
FILE: test/fixtures/bpmn/import/labels/external.bpmn
================================================
SequenceFlow_1
SequenceFlow_1
SequenceFlow_5
================================================
FILE: test/fixtures/bpmn/import/multiple-diagrams.bpmn
================================================
SequenceFlow_1
SequenceFlow_1
SequenceFlow_2
SequenceFlow_2
SequenceFlow_3
SequenceFlow_3
================================================
FILE: test/fixtures/bpmn/import/position/position-testcase.bpmn
================================================
ID_Flow_1
ID_Flow_1
ID_Flow_2
ID_Flow_2
================================================
FILE: test/fixtures/bpmn/import/process.bpmn
================================================
SequenceFlow_3
SequenceFlow_2
SequenceFlow_1
SequenceFlow_1
SequenceFlow_2
SequenceFlow_3
================================================
FILE: test/fixtures/bpmn/import/text-annotation-message-flow.bpmn
================================================
TextAnnotation_1
Flow_1wufvvv
Flow_12788mj
Flow_12788mj
Flow_1wufvvv
================================================
FILE: test/fixtures/bpmn/kitchen-sink.bpmn
================================================
Activity_1ketqts
Activity_1aw7nq3
Activity_09glvdf
Gateway_0w4fbsk
Gateway_0lwqw3q
Gateway_0im7nvs
Gateway_1gp70qr
Activity_088fmm5
Event_0m5cdgr
Event_1vk962b
Activity_0pzvbay
Event_1njhrf7
Event_0az045q
Event_0rd4z0d
Event_00twu86
Event_1faweyu
Event_01o5kun
Event_0a3nmhs
Event_0nn7qi2
Event_0cvqzwg
Event_0jf1q45
Event_0d3edlz
Event_0mf406j
Event_1tb0m8u
Event_1folo0q
Event_1lhkn4m
Event_0tlcssq
Event_1d1oeak
Event_1bjbr5h
Activity_0djfr8f
Activity_08egzqv
Activity_1125q3g
Activity_1ldvx66
Activity_011s9cd
Activity_1a96i3e
Activity_0e5q7rs
Gateway_1la6tas
Gateway_01lyed4
Event_1ks0id1
Event_0j90wfw
Event_0ikqxya
Event_1x9von4
Event_13xao98
Event_1pdjezb
Event_1ks4j21
Event_0ncmtxn
Event_01qiiyi
Event_1cax1xl
Event_1o9cjfb
Event_0t86sxc
Event_11fzkjc
Activity_0hcpwc9
Activity_0vheewc
Activity_1sosl74
Activity_0brc3us
Gateway_0238ieb
Activity_0pfhghz
Activity_0gi9n9o
Activity_1ivfc6a
Activity_1ndsz41
Activity_0g37k20
Activity_0r6ceyw
Activity_137lgd1
Activity_1fi82ot
Event_11sdw9z
Flow_0802uvc
DataStoreReference_1gynygb
Property_0bs4ymr
DataObjectReference_0v2e6m7
Property_0bs4ymr
Flow_0802uvc
DataStoreReference_1wvmfek
DataObjectReference_05ngfcy
Flow_0a4nevl
Flow_0a4nevl
Flow_16zflj8
Flow_16zflj8
Textannotation
================================================
FILE: test/fixtures/bpmn/multiple-diagrams-lanesets.bpmn
================================================
SequenceFlow
SequenceFlow
================================================
FILE: test/fixtures/bpmn/multiple-diagrams-overlapping-di.bpmn
================================================
SequenceFlow
SequenceFlow
================================================
FILE: test/fixtures/bpmn/multiple-diagrams.bpmn
================================================
================================================
FILE: test/fixtures/bpmn/multiple-nested-processes.bpmn
================================================
================================================
FILE: test/fixtures/bpmn/nested-subprocesses.bpmn
================================================
SequenceFlow_1
SequenceFlow_4
SequenceFlow_5
SequenceFlow_2
SequenceFlow_2
SequenceFlow_3
SequenceFlow_3
SequenceFlow_4
SequenceFlow_5
SequenceFlow_1
================================================
FILE: test/fixtures/bpmn/sequence-flows.bpmn
================================================
SequenceFlow_2
SequenceFlow_2
SequenceFlow_1
SequenceFlow_1
================================================
FILE: test/fixtures/bpmn/simple-resizable.bpmn
================================================
SequenceFlow_1
SequenceFlow_1
SequenceFlow_2
SequenceFlow_2
================================================
FILE: test/fixtures/bpmn/simple.bpmn
================================================
SequenceFlow_3
SequenceFlow_2
SequenceFlow_1
SequenceFlow_1
SequenceFlow_2
SequenceFlow_3
================================================
FILE: test/fixtures/json/model/camunda.json
================================================
{
"name": "Camunda",
"uri": "http://camunda.org/schema/1.0/bpmn",
"prefix": "camunda",
"xml": {
"tagAlias": "lowerCase"
},
"associations": [],
"types": [
{
"name": "InOutBinding",
"superClass": [
"Element"
],
"isAbstract": true,
"properties": [
{
"name": "source",
"isAttr": true,
"type": "String"
},
{
"name": "sourceExpression",
"isAttr": true,
"type": "String"
},
{
"name": "target",
"isAttr": true,
"type": "String"
},
{
"name": "businessKey",
"isAttr": true,
"type": "String"
},
{
"name": "local",
"isAttr": true,
"type": "Boolean",
"default": false
},
{
"name": "variables",
"isAttr": true,
"type": "String"
}
]
},
{
"name": "In",
"superClass": [
"InOutBinding"
],
"meta": {
"allowedIn": [
"bpmn:CallActivity"
]
}
},
{
"name": "Out",
"superClass": [
"InOutBinding"
],
"meta": {
"allowedIn": [
"bpmn:CallActivity"
]
}
},
{
"name": "AsyncCapable",
"isAbstract": true,
"extends": [
"bpmn:Activity",
"bpmn:Gateway",
"bpmn:Event"
],
"properties": [
{
"name": "async",
"isAttr": true,
"type": "Boolean",
"default": false
},
{
"name": "asyncBefore",
"isAttr": true,
"type": "Boolean",
"default": false
},
{
"name": "asyncAfter",
"isAttr": true,
"type": "Boolean",
"default": false
},
{
"name": "exclusive",
"isAttr": true,
"type": "Boolean",
"default": true
}
]
},
{
"name": "JobPriorized",
"isAbstract": true,
"extends": [
"bpmn:Process",
"camunda:AsyncCapable"
],
"properties": [
{
"name": "jobPriority",
"isAttr": true,
"type": "String"
}
]
},
{
"name": "SignalEventDefinition",
"isAbstract": true,
"extends": [
"bpmn:SignalEventDefinition"
],
"properties": [
{
"name": "async",
"isAttr": true,
"type": "Boolean",
"default": false
}
]
},
{
"name": "ErrorEventDefinition",
"isAbstract": true,
"extends": [
"bpmn:ErrorEventDefinition"
],
"properties": [
{
"name": "errorCodeVariable",
"isAttr": true,
"type": "String"
},
{
"name": "errorMessageVariable",
"isAttr": true,
"type": "String"
}
]
},
{
"name": "PotentialStarter",
"superClass": [
"Element"
],
"properties": [
{
"name": "resourceAssignmentExpression",
"type": "bpmn:ResourceAssignmentExpression"
}
]
},
{
"name": "FormSupported",
"isAbstract": true,
"extends": [
"bpmn:StartEvent",
"bpmn:UserTask"
],
"properties": [
{
"name": "formHandlerClass",
"isAttr": true,
"type": "String"
},
{
"name": "formKey",
"isAttr": true,
"type": "String"
}
]
},
{
"name": "Initiator",
"isAbstract": true,
"extends": [ "bpmn:StartEvent" ],
"properties": [
{
"name": "initiator",
"isAttr": true,
"type": "String"
}
]
},
{
"name": "ScriptTask",
"isAbstract": true,
"extends": [
"bpmn:ScriptTask"
],
"properties": [
{
"name": "resultVariable",
"isAttr": true,
"type": "String"
},
{
"name": "resource",
"isAttr": true,
"type": "String"
}
]
},
{
"name": "Process",
"isAbstract": true,
"extends": [
"bpmn:Process"
],
"properties": [
{
"name": "candidateStarterGroups",
"isAttr": true,
"type": "String"
},
{
"name": "candidateStarterUsers",
"isAttr": true,
"type": "String"
},
{
"name": "versionTag",
"isAttr": true,
"type": "String"
}
]
},
{
"name": "EscalationEventDefinition",
"isAbstract": true,
"extends": [
"bpmn:EscalationEventDefinition"
],
"properties": [
{
"name": "escalationCodeVariable",
"isAttr": true,
"type": "String"
}
]
},
{
"name": "FormalExpression",
"isAbstract": true,
"extends": [
"bpmn:FormalExpression"
],
"properties": [
{
"name": "resource",
"isAttr": true,
"type": "String"
}
]
},
{
"name": "Assignable",
"extends": [ "bpmn:UserTask" ],
"properties": [
{
"name": "assignee",
"isAttr": true,
"type": "String"
},
{
"name": "candidateUsers",
"isAttr": true,
"type": "String"
},
{
"name": "candidateGroups",
"isAttr": true,
"type": "String"
},
{
"name": "dueDate",
"isAttr": true,
"type": "String"
},
{
"name": "followUpDate",
"isAttr": true,
"type": "String"
},
{
"name": "priority",
"isAttr": true,
"type": "String"
}
]
},
{
"name": "CallActivity",
"extends": [ "bpmn:CallActivity" ],
"properties": [
{
"name": "calledElementBinding",
"isAttr": true,
"type": "String",
"default": "latest"
},
{
"name": "calledElementVersion",
"isAttr": true,
"type": "String"
},
{
"name": "calledElementTenantId",
"isAttr": true,
"type": "String"
},
{
"name": "caseRef",
"isAttr": true,
"type": "String"
},
{
"name": "caseBinding",
"isAttr": true,
"type": "String",
"default": "latest"
},
{
"name": "caseVersion",
"isAttr": true,
"type": "String"
},
{
"name": "caseTenantId",
"isAttr": true,
"type": "String"
},
{
"name": "variableMappingClass",
"isAttr": true,
"type": "String"
},
{
"name": "variableMappingDelegateExpression",
"isAttr": true,
"type": "String"
}
]
},
{
"name": "ServiceTaskLike",
"extends": [
"bpmn:ServiceTask",
"bpmn:BusinessRuleTask",
"bpmn:SendTask",
"bpmn:MessageEventDefinition"
],
"properties": [
{
"name": "expression",
"isAttr": true,
"type": "String"
},
{
"name": "class",
"isAttr": true,
"type": "String"
},
{
"name": "delegateExpression",
"isAttr": true,
"type": "String"
},
{
"name": "resultVariable",
"isAttr": true,
"type": "String"
}
]
},
{
"name": "DmnCapable",
"extends": [
"bpmn:BusinessRuleTask"
],
"properties": [
{
"name": "decisionRef",
"isAttr": true,
"type": "String"
},
{
"name": "decisionRefBinding",
"isAttr": true,
"type": "String",
"default": "latest"
},
{
"name": "decisionRefVersion",
"isAttr": true,
"type": "String"
},
{
"name": "mapDecisionResult",
"isAttr": true,
"type": "String",
"default": "resultList"
},
{
"name": "decisionRefTenantId",
"isAttr": true,
"type": "String"
}
]
},
{
"name": "ExternalCapable",
"extends": [
"camunda:ServiceTaskLike"
],
"properties": [
{
"name": "type",
"isAttr": true,
"type": "String"
},
{
"name": "topic",
"isAttr": true,
"type": "String"
}
]
},
{
"name": "TaskPriorized",
"extends": [
"bpmn:Process",
"camunda:ExternalCapable"
],
"properties": [
{
"name": "taskPriority",
"isAttr": true,
"type": "String"
}
]
},
{
"name": "Properties",
"superClass": [
"Element"
],
"meta": {
"allowedIn": [ "*" ]
},
"properties": [
{
"name": "values",
"type": "Property",
"isMany": true
}
]
},
{
"name": "Property",
"superClass": [
"Element"
],
"properties": [
{
"name": "id",
"type": "String",
"isAttr": true
},
{
"name": "name",
"type": "String",
"isAttr": true
},
{
"name": "value",
"type": "String",
"isAttr": true
}
]
},
{
"name": "Connector",
"superClass": [
"Element"
],
"meta": {
"allowedIn": [
"bpmn:ServiceTask",
"bpmn:BusinessRuleTask",
"bpmn:SendTask"
]
},
"properties": [
{
"name": "inputOutput",
"type": "InputOutput"
},
{
"name": "connectorId",
"type": "String"
}
]
},
{
"name": "InputOutput",
"superClass": [
"Element"
],
"meta": {
"allowedIn": [
"bpmn:Task",
"bpmn:UserTask",
"bpmn:ServiceTask",
"bpmn:SendTask",
"bpmn:BusinessRuleTask",
"bpmn:ReceiveTask",
"bpmn:ScriptTask",
"bpmn:ManualTask",
"bpmn:GlobalUserTask",
"bpmn:GlobalScriptTask",
"bpmn:GlobalBusinessRuleTask",
"bpmn:GlobalTask",
"bpmn:GlobalManualTask",
"bpmn:SubProcess",
"bpmn:Transaction",
"bpmn:IntermediateCatchEvent",
"bpmn:IntermediateThrowEvent",
"bpmn:EndEvent",
"bpmn:ThrowEvent",
"bpmn:CatchEvent",
"bpmn:ImplicitThrowEvent",
"bpmn:CallActivity"
]
},
"properties": [
{
"name": "inputOutput",
"type": "InputOutput"
},
{
"name": "connectorId",
"type": "String"
},
{
"name": "inputParameters",
"isMany": true,
"type": "InputParameter"
},
{
"name": "outputParameters",
"isMany": true,
"type": "OutputParameter"
}
]
},
{
"name": "InputOutputParameter",
"properties": [
{
"name": "name",
"isAttr": true,
"type": "String"
},
{
"name": "value",
"isBody": true,
"type": "String"
},
{
"name": "definition",
"type": "InputOutputParameterDefinition"
}
]
},
{
"name": "InputOutputParameterDefinition",
"isAbstract": true
},
{
"name": "List",
"superClass": [ "InputOutputParameterDefinition" ],
"properties": [
{
"name": "items",
"isMany": true,
"type": "InputOutputParameterDefinition"
}
]
},
{
"name": "Map",
"superClass": [ "InputOutputParameterDefinition" ],
"properties": [
{
"name": "entries",
"isMany": true,
"type": "Entry"
}
]
},
{
"name": "Entry",
"properties": [
{
"name": "key",
"isAttr": true,
"type": "String"
},
{
"name": "value",
"isBody": true,
"type": "String"
},
{
"name": "definition",
"type": "InputOutputParameterDefinition"
}
]
},
{
"name": "Value",
"superClass": [
"InputOutputParameterDefinition"
],
"properties": [
{
"name": "id",
"isAttr": true,
"type": "String"
},
{
"name": "name",
"isAttr": true,
"type": "String"
},
{
"name": "value",
"isBody": true,
"type": "String"
}
]
},
{
"name": "Script",
"superClass": [ "InputOutputParameterDefinition" ],
"properties": [
{
"name": "scriptFormat",
"isAttr": true,
"type": "String"
},
{
"name": "resource",
"isAttr": true,
"type": "String"
},
{
"name": "value",
"isBody": true,
"type": "String"
}
]
},
{
"name": "Field",
"superClass": [ "Element" ],
"meta": {
"allowedIn": [
"bpmn:ServiceTask",
"bpmn:BusinessRuleTask",
"bpmn:SendTask"
]
},
"properties": [
{
"name": "name",
"isAttr": true,
"type": "String"
},
{
"name": "expression",
"type": "String"
},
{
"name": "stringValue",
"isAttr": true,
"type": "String"
},
{
"name": "string",
"type": "String"
}
]
},
{
"name": "InputParameter",
"superClass": [ "InputOutputParameter" ]
},
{
"name": "OutputParameter",
"superClass": [ "InputOutputParameter" ]
},
{
"name": "Collectable",
"isAbstract": true,
"extends": [ "bpmn:MultiInstanceLoopCharacteristics" ],
"superClass": [ "camunda:AsyncCapable" ],
"properties": [
{
"name": "collection",
"isAttr": true,
"type": "String"
},
{
"name": "elementVariable",
"isAttr": true,
"type": "String"
}
]
},
{
"name": "FailedJobRetryTimeCycle",
"superClass": [ "Element" ],
"meta": {
"allowedIn": [
"bpmn:Task",
"bpmn:ServiceTask",
"bpmn:SendTask",
"bpmn:UserTask",
"bpmn:BusinessRuleTask",
"bpmn:ScriptTask",
"bpmn:ReceiveTask",
"bpmn:CallActivity",
"bpmn:TimerEventDefinition",
"bpmn:SignalEventDefinition",
"bpmn:MultiInstanceLoopCharacteristics"
]
},
"properties": [
{
"name": "body",
"isBody": true,
"type": "String"
}
]
},
{
"name": "ExecutionListener",
"superClass": [ "Element" ],
"meta": {
"allowedIn": [
"bpmn:Task",
"bpmn:ServiceTask",
"bpmn:UserTask",
"bpmn:BusinessRuleTask",
"bpmn:ScriptTask",
"bpmn:ReceiveTask",
"bpmn:ManualTask",
"bpmn:ExclusiveGateway",
"bpmn:SequenceFlow",
"bpmn:ParallelGateway",
"bpmn:InclusiveGateway",
"bpmn:EventBasedGateway",
"bpmn:StartEvent",
"bpmn:IntermediateCatchEvent",
"bpmn:IntermediateThrowEvent",
"bpmn:EndEvent",
"bpmn:BoundaryEvent",
"bpmn:CallActivity",
"bpmn:SubProcess"
]
},
"properties": [
{
"name": "expression",
"isAttr": true,
"type": "String"
},
{
"name": "class",
"isAttr": true,
"type": "String"
},
{
"name": "delegateExpression",
"isAttr": true,
"type": "String"
},
{
"name": "event",
"isAttr": true,
"type": "String"
},
{
"name": "script",
"type": "Script"
},
{
"name": "fields",
"type": "Field",
"isMany": true
}
]
},
{
"name": "TaskListener",
"superClass": [ "Element" ],
"meta": {
"allowedIn": [
"bpmn:UserTask"
]
},
"properties": [
{
"name": "expression",
"isAttr": true,
"type": "String"
},
{
"name": "class",
"isAttr": true,
"type": "String"
},
{
"name": "delegateExpression",
"isAttr": true,
"type": "String"
},
{
"name": "event",
"isAttr": true,
"type": "String"
},
{
"name": "script",
"type": "Script"
},
{
"name": "fields",
"type": "Field",
"isMany": true
}
]
},
{
"name": "FormProperty",
"superClass": [ "Element" ],
"meta": {
"allowedIn": [
"bpmn:StartEvent",
"bpmn:UserTask"
]
},
"properties": [
{
"name": "id",
"type": "String",
"isAttr": true
},
{
"name": "name",
"type": "String",
"isAttr": true
},
{
"name": "type",
"type": "String",
"isAttr": true
},
{
"name": "required",
"type": "String",
"isAttr": true
},
{
"name": "readable",
"type": "String",
"isAttr": true
},
{
"name": "writable",
"type": "String",
"isAttr": true
},
{
"name": "variable",
"type": "String",
"isAttr": true
},
{
"name": "expression",
"type": "String",
"isAttr": true
},
{
"name": "datePattern",
"type": "String",
"isAttr": true
},
{
"name": "default",
"type": "String",
"isAttr": true
},
{
"name": "values",
"type": "Value",
"isMany": true
}
]
},
{
"name": "FormData",
"superClass": [ "Element" ],
"meta": {
"allowedIn": [
"bpmn:StartEvent",
"bpmn:UserTask"
]
},
"properties": [
{
"name": "fields",
"type": "FormField",
"isMany": true
},
{
"name": "businessKey",
"type": "String",
"isAttr": true
}
]
},
{
"name": "FormField",
"superClass": [ "Element" ],
"properties": [
{
"name": "id",
"type": "String",
"isAttr": true
},
{
"name": "label",
"type": "String",
"isAttr": true
},
{
"name": "type",
"type": "String",
"isAttr": true
},
{
"name": "datePattern",
"type": "String",
"isAttr": true
},
{
"name": "defaultValue",
"type": "String",
"isAttr": true
},
{
"name": "properties",
"type": "Properties"
},
{
"name": "validation",
"type": "Validation"
},
{
"name": "values",
"type": "Value",
"isMany": true
}
]
},
{
"name": "Validation",
"superClass": [ "Element" ],
"properties": [
{
"name": "constraints",
"type": "Constraint",
"isMany": true
}
]
},
{
"name": "Constraint",
"superClass": [ "Element" ],
"properties": [
{
"name": "name",
"type": "String",
"isAttr": true
},
{
"name": "config",
"type": "String",
"isAttr": true
}
]
},
{
"name": "ConditionalEventDefinition",
"isAbstract": true,
"extends": [
"bpmn:ConditionalEventDefinition"
],
"properties": [
{
"name": "variableName",
"isAttr": true,
"type": "String"
},
{
"name": "variableEvent",
"isAttr": true,
"type": "String"
}
]
}
],
"emumerations": [ ]
}
================================================
FILE: test/fixtures/json/model/custom-override.json
================================================
{
"name": "Custom descriptor Override",
"uri": "http://customdescriptor.com/bpmn2",
"prefix": "custom",
"xml": {
"tagAlias": "lowerCase"
},
"associations": [],
"types": [
{
"name": "CustomSendElementOverride",
"superClass": [
"Element"
],
"properties": [
{
"name": "name",
"isAttr": true,
"type": "String"
},
{
"name": "value",
"isAttr": true,
"type": "String"
}
]
},
{
"name": "ServiceTaskGroupOverride",
"extends": [
"bpmn:ServiceTask",
"bpmn:BusinessRuleTask",
"bpmn:SendTask",
"bpmn:MessageEventDefinition"
]
}
],
"emumerations": [ ]
}
================================================
FILE: test/fixtures/json/model/custom.json
================================================
{
"name": "Custom descriptor",
"uri": "http://customdescriptor.com/bpmn2",
"prefix": "custom",
"xml": {
"tagAlias": "lowerCase"
},
"associations": [],
"types": [
{
"name": "CustomSendElement",
"superClass": [
"Element"
],
"properties": [
{
"name": "name",
"isAttr": true,
"type": "String"
},
{
"name": "value",
"isAttr": true,
"type": "String"
},
{
"name": "paths",
"type": "String",
"isMany": true
}
]
},
{
"name": "ServiceTaskGroup",
"extends": [
"bpmn:ServiceTask",
"bpmn:BusinessRuleTask",
"bpmn:SendTask",
"bpmn:MessageEventDefinition"
]
}
],
"emumerations": [ ]
}
================================================
FILE: test/helper/TranslationCollector.js
================================================
import translate from 'diagram-js/lib/i18n/translate/translate';
function collectTranslations(template, replacements) {
var log = {
type: 'translations',
msg: template
};
console.log(JSON.stringify(log));
return translate(template, replacements);
}
export default {
translate: [ 'value', collectTranslations ]
};
================================================
FILE: test/helper/index.js
================================================
/**
* A helper file that may be used in test cases for bpmn-js and extensions.
*
* Provides the globals
*
* * bootstrapModeler(): bootstrap a modeler instance
* * bootstrapViewer(): bootstrap a viewer instance
* * inject(function(a, b) {}): inject the bpmn-js services in the given function
*
*
* In addition it provides the utilities
*
* * insertCSS(name, css): add a CSS file to be used in test cases
*
*
* It is recommended to expose the helper through a per-project utility and
* and perform custom bootstrapping (CSS, ...) in that utility.
*
* ```
* export * from 'bpmn-js/test/helper';
*
* import {
* insertCSS
* } from 'bpmn-js/test/helper';
*
* // insert diagram.css
* insertCSS('diagram.css', require('./some-css.css'));
* ```
*/
import {
isFunction,
forEach,
merge
} from 'min-dash';
import TestContainer from 'mocha-test-container-support';
import Modeler from '../../lib/Modeler';
import NavigatedViewer from '../../lib/NavigatedViewer';
import Viewer from '../../lib/Viewer';
var OPTIONS, BPMN_JS;
import translationModule from './TranslationCollector';
export var collectTranslations = window.__env__ && window.__env__.COLLECT_TRANSLATIONS;
// inject logging translation module into default modules
if (collectTranslations) {
[ Modeler, Viewer, NavigatedViewer ].forEach(function(constructor) {
constructor.prototype._modules.push(translationModule);
});
}
export function bootstrapBpmnJS(BpmnJS, diagram, options, locals) {
return function() {
var testContainer;
// Make sure the test container is an optional dependency and we fall back
// to an empty if it does not exist.
//
// This is needed if other libraries rely on this helper for testing
// while not adding the mocha-test-container-support as a dependency.
try {
// 'this' is the current test context
testContainer = TestContainer.get(this);
} catch (e) {
testContainer = document.createElement('div');
testContainer.classList.add('test-content-container');
document.body.appendChild(testContainer);
}
var _options = options,
_locals = locals;
if (_locals === undefined && isFunction(_options)) {
_locals = _options;
_options = null;
}
if (isFunction(_options)) {
_options = _options();
}
if (isFunction(_locals)) {
_locals = _locals();
}
_options = merge({
container: testContainer,
canvas: {
deferUpdate: false
}
}, OPTIONS, _options);
if (_locals) {
var mockModule = {};
forEach(_locals, function(v, k) {
mockModule[k] = [ 'value', v ];
});
_options.modules = [].concat(_options.modules || [], [ mockModule ]);
}
if (_options.modules && !_options.modules.length) {
_options.modules = undefined;
}
// used to extract translations used during tests
if (collectTranslations) {
_options.additionalModules = [].concat(
_options.additionalModules || [],
[ translationModule ]
);
}
clearBpmnJS();
var instance = new BpmnJS(_options);
setBpmnJS(instance);
return instance.importXML(diagram).then(function(result) {
return { error: null, warnings: result.warnings };
}).catch(function(err) {
return { error: err, warnings: err.warnings };
});
};
}
/**
* Bootstrap the Modeler given the specified options and a number of locals (i.e. services)
*
* @example
*
* describe(function() {
*
* var mockEvents;
*
* beforeEach(bootstrapModeler('some-xml', function() {
* mockEvents = new Events();
*
* return {
* events: mockEvents
* };
* }));
*
* });
*
* @param {string} xml document to display
* @param {Object} (options) optional options to be passed to the diagram upon instantiation
* @param {Object|Function} locals the local overrides to be used by the diagram or a function that produces them
* @return {Function} a function to be passed to beforeEach
*/
export function bootstrapModeler(diagram, options, locals) {
return bootstrapBpmnJS(Modeler, diagram, options, locals);
}
/**
* Bootstrap the Viewer given the specified options and a number of locals (i.e. services)
*
* @example
*
* describe(function() {
*
* var mockEvents;
*
* beforeEach(bootstrapViewer('some-xml', function() {
* mockEvents = new Events();
*
* return {
* events: mockEvents
* };
* }));
*
* });
*
* @param {string} xml document to display
* @param {Object} (options) optional options to be passed to the diagram upon instantiation
* @param {Object|Function} locals the local overrides to be used by the diagram or a function that produces them
* @return {Function} a function to be passed to beforeEach
*/
export function bootstrapViewer(diagram, options, locals) {
return bootstrapBpmnJS(Viewer, diagram, options, locals);
}
/**
* Injects services of an instantiated diagram into the argument.
*
* Use it in conjunction with {@link #bootstrapModeler} or {@link #bootstrapViewer}.
*
* @example
*
* describe(function() {
*
* var mockEvents;
*
* beforeEach(bootstrapViewer(...));
*
* it('should provide mocked events', inject(function(events) {
* expect(events).to.eql(mockEvents);
* }));
*
* });
*
* @param {Function} fn the function to inject to
* @return {Function} a function that can be passed to it to carry out the injection
*/
export function inject(fn) {
return function() {
if (!BPMN_JS) {
throw new Error(
'no bootstraped bpmn-js instance, ' +
'ensure you created it via #boostrap(Modeler|Viewer)'
);
}
return BPMN_JS.invoke(fn);
};
}
/**
* Returns the current active BpmnJS instance.
*
* @return {BpmnJS}
*/
export function getBpmnJS() {
return BPMN_JS;
}
export function clearBpmnJS() {
// clean up old bpmn-js instance
if (BPMN_JS) {
BPMN_JS.destroy();
BPMN_JS = null;
}
}
// This method always resolves.
// It helps us to do done(err) within the same block.
export function createViewer(container, viewerInstance, xml, diagramId) {
clearBpmnJS();
var viewer = new viewerInstance({ container: container });
setBpmnJS(viewer);
return viewer.importXML(xml, diagramId).then(function(result) {
return { warnings: result.warnings, viewer: viewer };
}).catch(function(err) {
return { error: err, viewer: viewer, warnings: err.warnings };
});
}
function logConfigured(type, force) {
var url = new URL(window.location.href);
var log = ('searchParams' in url) && url.searchParams.get('log') || '';
return force || log.includes('save-xml');
}
/**
* Enable logging on a modeler instance.
*
* @param {import('bpmn-js')} modeler
* @param {boolean} [force=false]
*/
export function enableLogging(modeler, force) {
var saveXML = logConfigured('save-xml', force);
saveXML && modeler.on('commandStack.changed', function() {
Promise.resolve()
.then(() => modeler.saveXML({ format: true }))
.then((result) => console.log(result.xml));
});
}
export function setBpmnJS(instance) {
BPMN_JS = instance;
}
export function insertCSS(name, css) {
if (document.querySelector('[data-css-file="' + name + '"]')) {
return;
}
var head = document.head || document.getElementsByTagName('head')[0],
style = document.createElement('style');
style.setAttribute('data-css-file', name);
style.type = 'text/css';
if (style.styleSheet) {
style.styleSheet.cssText = css;
} else {
style.appendChild(document.createTextNode(css));
}
head.appendChild(style);
}
================================================
FILE: test/integration/CustomElementsSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import Modeler from 'lib/Modeler';
import {
createCanvasEvent as canvasEvent
} from '../util/MockEvents';
import customElementsModules from './custom-elements';
var modelerModules = Modeler.prototype._modules,
customModules = [ customElementsModules ];
var testModules = [].concat(modelerModules, customModules);
var processDiagramXML = require('../fixtures/bpmn/simple.bpmn');
var collaborationDiagramXML = require('../fixtures/bpmn/collaboration.bpmn');
describe('custom elements', function() {
describe('renderer', function() {
beforeEach(bootstrapModeler(processDiagramXML, {
modules: testModules
}));
var triangle, circle;
beforeEach(inject(function(elementFactory, canvas) {
triangle = elementFactory.createShape({
id: 'triangle',
type: 'custom:triangle',
x: 700, y: 100
});
canvas.addShape(triangle);
circle = elementFactory.createShape({
id: 'circle',
type: 'custom:circle',
x: 800, y: 100
});
canvas.addShape(circle);
}));
it('should render custom elements', inject(function(elementRegistry) {
// when
// then
expect(elementRegistry.get('triangle')).to.eql(triangle);
expect(elementRegistry.get('circle')).to.eql(circle);
}));
it('should get the correct custom elements path', inject(function(graphicsFactory) {
// when
var trianglePath = graphicsFactory.getShapePath(triangle),
circlePath = graphicsFactory.getShapePath(circle);
// then
expect(trianglePath).to.equal('M720,100l20,40l-40,0z');
expect(circlePath).to.equal('M870,170m0,-70a70,70,0,1,1,0,140a70,70,0,1,1,0,-140z');
}));
it('should still render bpmn elements', inject(function(elementFactory) {
// when
var startEvent = elementFactory.createShape({ type: 'bpmn:StartEvent' });
// then
expect(startEvent.businessObject.$type).to.equal('bpmn:StartEvent');
}));
});
describe('integration', function() {
describe('process diagram', function() {
beforeEach(bootstrapModeler(processDiagramXML, {
modules: testModules
}));
var triangle, circle;
beforeEach(inject(function(elementFactory, canvas) {
circle = elementFactory.createShape({
id: 'circle',
type: 'custom:circle',
x: 800, y: 100
});
canvas.addShape(circle);
triangle = elementFactory.createShape({
id: 'triangle',
type: 'custom:triangle',
x: 700, y: 100
});
canvas.addShape(triangle);
}));
it('should allow moving a custom shape inside another one',
inject(function(elementFactory, elementRegistry, dragging, move) {
// given
var circleGfx = elementRegistry.getGraphics(circle);
// when
move.start(canvasEvent({ x: 0, y: 0 }), triangle);
dragging.move(canvasEvent({ x: 100, y: 0 }));
dragging.hover({ element: circle, gfx: circleGfx });
dragging.move(canvasEvent({ x: 150, y: 50 }));
dragging.end();
// then
expect(triangle.parent).to.equal(circle);
})
);
it('should update the custom shape properties',
inject(function(elementFactory, elementRegistry, dragging, move) {
// given
var circleGfx = elementRegistry.getGraphics(circle);
// when
move.start(canvasEvent({ x: 0, y: 0 }), triangle);
dragging.move(canvasEvent({ x: 100, y: 0 }));
dragging.hover({ element: circle, gfx: circleGfx });
dragging.move(canvasEvent({ x: 150, y: 50 }));
dragging.end();
// then
expect(triangle.businessObject.leader).to.equal(circle);
expect(circle.businessObject.companions).to.include(triangle);
})
);
it('should not connect a bpmn element to a custom one',
inject(function(elementFactory, dragging, elementRegistry, connect) {
// given
var subProcess = elementRegistry.get('SubProcess_1'),
triangleGfx = elementRegistry.getGraphics(triangle);
// when
connect.start(canvasEvent({ x: 590, y: 90 }), subProcess);
dragging.move(canvasEvent({ x: 700, y: 100 }));
dragging.hover({ element: triangle, gfx: triangleGfx });
dragging.move(canvasEvent({ x: 715, y: 115 }));
dragging.end();
// then
expect(triangle.incoming).to.have.lengthOf(0);
})
);
});
describe('collaboration diagram', function() {
beforeEach(bootstrapModeler(collaborationDiagramXML, {
modules: testModules
}));
var triangle;
beforeEach(inject(function(elementFactory, canvas) {
triangle = elementFactory.createShape({
id: 'triangle',
type: 'custom:triangle',
x: 700, y: 100
});
canvas.addShape(triangle);
}));
it('should update parent when removing collaboration',
inject(function(elementRegistry, modeling, canvas) {
// given
var customTriangle = elementRegistry.get('triangle');
// when
modeling.removeElements([
elementRegistry.get('Participant_1'),
elementRegistry.get('Participant_2')
]);
// then
expect(customTriangle.parent).to.eql(canvas.getRootElement());
})
);
});
});
});
================================================
FILE: test/integration/ReimportSpec.js
================================================
import Modeler from 'lib/Modeler';
import TestContainer from 'mocha-test-container-support';
function delay(fn) {
setTimeout(fn, 10);
}
describe.skip('scenario - successive reopening', function() {
var container;
beforeEach(function() {
container = TestContainer.get(this);
});
var boundaryXML = require('../fixtures/bpmn/boundary-events.bpmn'),
containersXML = require('../fixtures/bpmn/containers.bpmn'),
flowMarkersXML = require('../fixtures/bpmn/flow-markers.bpmn'),
simpleXML = require('../fixtures/bpmn/simple.bpmn');
var allDiagrams = [
boundaryXML,
containersXML,
flowMarkersXML,
simpleXML
];
it('should import 100 diagrams', function(done) {
// this test needs time
this.timeout(30000);
var count = 0;
// given
var modeler = new Modeler({ container: container });
modeler.on('import.done', function(event) {
console.log('imported #' + count);
if (event.error) {
console.error('ERROR', event.error);
}
if (event.warnings && event.warnings.length) {
console.warn('WARNINGS', event.warnings);
}
});
function finish(err) {
modeler.destroy();
done(err);
}
function importNext() {
if (count === 100) {
return finish();
}
var i = count % allDiagrams.length;
var xml = allDiagrams[i];
// when
modeler.importXML(xml).then(function() {
count++;
delay(importNext);
}).catch(function(err) {
return finish(err);
});
}
// when
importNext();
});
});
================================================
FILE: test/integration/SimpleModelingSpec.js
================================================
import Modeler from 'lib/Modeler';
import TestContainer from 'mocha-test-container-support';
describe('scenario - simple modeling', function() {
var container;
beforeEach(function() {
container = TestContainer.get(this);
});
it('should build process from start to end event', function() {
// given
var modeler = new Modeler({ container: container });
// when
return modeler.createDiagram();
});
});
================================================
FILE: test/integration/custom-elements/CustomElementFactory.js
================================================
import {
assign
} from 'min-dash';
import inherits from 'inherits-browser';
import ElementFactory from 'lib/features/modeling/ElementFactory';
import {
DEFAULT_LABEL_SIZE
} from 'lib/util/LabelUtil';
export default function CustomElementFactory(injector) {
injector.invoke(ElementFactory, this);
var self = this;
this.create = function(elementType, attrs) {
var type = attrs.type,
businessObject,
size;
if (elementType === 'label') {
return self._baseCreate(elementType, assign({ type: 'label' }, DEFAULT_LABEL_SIZE, attrs));
}
if (/^custom:/.test(type)) {
type = attrs.type.replace(/^custom:/, '');
businessObject = {};
size = self._getCustomElementSize(type);
return self._baseCreate(elementType,
assign({ type: elementType, businessObject: businessObject }, attrs, size));
}
return self.createElement(elementType, attrs);
};
}
inherits(CustomElementFactory, ElementFactory);
CustomElementFactory.$inject = [ 'injector' ];
/**
* Sets the *width* and *height* for custom shapes.
*
* The following example shows an interface on how
* to setup the custom element's dimensions.
*
* @example
*
* var shapes = {
* triangle: { width: 40, height: 40 },
* rectangle: { width: 100, height: 20 }
* };
*
* return shapes[type];
*
*
* @param {string} type
*
* @return {Bounds} { width, height}
*/
CustomElementFactory.prototype._getCustomElementSize = function(type) {
if (!type) {
return { width: 100, height: 80 };
}
var shapes = {
triangle: { width: 40, height: 40 },
circle: { width: 140, height: 140 }
};
return shapes[type];
};
================================================
FILE: test/integration/custom-elements/CustomRenderer.js
================================================
import inherits from 'inherits-browser';
import BaseRenderer from 'diagram-js/lib/draw/BaseRenderer';
import {
componentsToPath
} from 'diagram-js/lib/util/RenderUtil';
import {
append as svgAppend,
attr as svgAttr,
create as svgCreate
} from 'tiny-svg';
export default function CustomRenderer(eventBus, styles) {
BaseRenderer.call(this, eventBus, 2000);
this._styles = styles;
var self = this;
var computeStyle = styles.computeStyle;
this.handlers = {
'custom:triangle': function(parentGfx, element) {
return self.drawTriangle(parentGfx, element.width);
},
'custom:circle': function(parentGfx, element, attrs) {
return self.drawCircle(parentGfx, element.width, element.height, attrs);
}
};
this.drawTriangle = function(parentGfx, side, attrs) {
var halfSide = side / 2,
points;
points = [ { x: halfSide, y: 0 }, { x: side, y: side }, { x: 0, y: side } ];
var pointsString = points.map(function(point) {
return point.x + ',' + point.y;
}).join(' ');
attrs = computeStyle(attrs, {
stroke: '#3CAA82',
strokeWidth: 2,
fill: '#3CAA82'
});
var polygon = svgCreate('polygon');
svgAttr(polygon, { points: pointsString });
svgAttr(polygon, attrs);
svgAppend(parentGfx, polygon);
return polygon;
};
this.getTrianglePath = function(element) {
var x = element.x,
y = element.y,
width = element.width,
height = element.height;
var trianglePath = [
[ 'M', x + width / 2, y ],
[ 'l', width / 2, height ],
[ 'l', -width, 0 ],
[ 'z' ]
];
return componentsToPath(trianglePath);
};
this.drawCircle = function(parentGfx, width, height, attrs) {
var cx = width / 2,
cy = height / 2;
attrs = computeStyle(attrs, {
stroke: '#4488aa',
strokeWidth: 4,
fill: 'white'
});
var circle = svgCreate('circle');
svgAttr(circle, {
cx: cx,
cy: cy,
r: Math.round((width + height) / 4)
});
svgAttr(circle, attrs);
svgAppend(parentGfx, circle);
return circle;
};
this.getCirclePath = function(shape) {
var cx = shape.x + shape.width / 2,
cy = shape.y + shape.height / 2,
radius = shape.width / 2;
var circlePath = [
[ 'M', cx, cy ],
[ 'm', 0, -radius ],
[ 'a', radius, radius, 0, 1, 1, 0, 2 * radius ],
[ 'a', radius, radius, 0, 1, 1, 0, -2 * radius ],
[ 'z' ]
];
return componentsToPath(circlePath);
};
}
inherits(CustomRenderer, BaseRenderer);
CustomRenderer.$inject = [ 'eventBus', 'styles' ];
CustomRenderer.prototype.canRender = function(element) {
return /^custom:/.test(element.type);
};
CustomRenderer.prototype.drawShape = function(visuals, element) {
var type = element.type;
var h = this.handlers[type];
/* jshint -W040 */
return h(visuals, element);
};
CustomRenderer.prototype.drawConnection = function(visuals, element) {
var type = element.type;
var h = this.handlers[type];
/* jshint -W040 */
return h(visuals, element);
};
CustomRenderer.prototype.getShapePath = function(element) {
var type = element.type.replace(/^custom:/, '');
var shapes = {
triangle: this.getTrianglePath,
circle: this.getCirclePath
};
return shapes[type](element);
};
================================================
FILE: test/integration/custom-elements/CustomRules.js
================================================
import { forEach } from 'min-dash';
import inherits from 'inherits-browser';
import RuleProvider from 'diagram-js/lib/features/rules/RuleProvider';
var HIGH_PRIORITY = 1500;
/**
* Specific rules for custom elements
*/
export default function CustomRules(eventBus) {
RuleProvider.call(this, eventBus);
}
inherits(CustomRules, RuleProvider);
CustomRules.$inject = [ 'eventBus' ];
CustomRules.prototype.init = function() {
this.addRule('connection.create', HIGH_PRIORITY, function(context) {
var source = context.source,
target = context.target;
return canConnect(source, target);
});
this.addRule('connection.reconnect', HIGH_PRIORITY, function(context) {
var connection = context.connection,
source = context.source,
target = connection.target;
return canConnect(source, target, connection);
});
this.addRule('connection.updateWaypoints', HIGH_PRIORITY, function(context) {
// OK! but visually ignore
return null;
});
this.addRule('elements.move', HIGH_PRIORITY, function(context) {
var target = context.target,
shapes = context.shapes,
position = context.position;
return canMove(shapes, target, position);
});
this.addRule('shape.create', HIGH_PRIORITY, function(context) {
var target = context.target,
shape = context.shape,
position = context.position;
return canCreate(shape, target, position);
});
this.addRule('shape.resize', HIGH_PRIORITY, function(context) {
var shape = context.shape;
if (isCustom(shape)) {
return false;
}
});
};
function canConnect(source, target) {
return !isCustom(source) && !isCustom(target);
}
function canCreate(shape, target) {
if (isType(target, 'custom:triangle')) {
return false;
}
if (isType(target, 'custom:circle')) {
if (isType(shape, 'custom:triangle')) {
return true;
}
return false;
}
if (isCustom(shape)) {
return true;
}
}
function canMove(shapes, target, position) {
var result;
forEach(shapes, function(shape) {
if (isType(shape, 'custom:triangle') && isType(target, 'custom:circle')) {
result = true;
return false;
}
if (isCustom(target)) {
result = false;
return false;
}
if (isCustom(shape)) {
result = true;
return false;
}
});
return result;
}
function isType(element, type) {
var patt = new RegExp(type, 'i');
return element && patt.test(element.type);
}
function isCustom(element) {
return element && /^custom:/.test(element.type);
}
================================================
FILE: test/integration/custom-elements/CustomUpdater.js
================================================
import inherits from 'inherits-browser';
import {
is as isBpmn
} from 'lib/util/ModelUtil';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
function isCustom(element, type) {
if (!type) {
return /custom:/.test(element.type);
}
return element && element.type === type;
}
function ifCustomElement(fn) {
return function(event) {
var context = event.context,
element = context.shape || context.connection;
if (!isBpmn(element, 'bpmn:BaseElement')) {
fn(event);
}
};
}
/**
* A handler responsible for updating the custom element's businessObject
* once changes on the diagram happen
*/
export default function CustomUpdater(eventBus, modeling) {
CommandInterceptor.call(this, eventBus);
function updateTriangle(evt) {
var context = evt.context,
shape = context.shape,
businessObject = shape.businessObject,
leader = businessObject.leader,
companions,
parent,
idx;
if (!isCustom(shape, 'custom:triangle')) {
return;
}
parent = shape.parent;
if (!parent) {
return;
}
if (isBpmn(parent, 'bpmn:SubProcess')) {
shape.businessObject.foo = 'geil';
}
if (!isBpmn(parent, 'bpmn:SubProcess')) {
shape.businessObject.foo = 'bar';
}
if (isCustom(parent, 'custom:circle')) {
shape.businessObject.leader = parent;
if (!parent.businessObject.companions) {
parent.businessObject.companions = [];
}
parent.businessObject.companions.push(shape);
}
if (!isCustom(parent, 'custom:circle') && leader) {
companions = leader.businessObject.companions;
idx = companions.indexOf(shape);
companions.splice(idx, 1);
businessObject.leader = '';
}
}
this.executed([
'shape.move',
'shape.create'
], ifCustomElement(updateTriangle));
/**
* When morphing a Process into a Collaboration or vice-versa,
* make sure that the existing custom elements get their parents updated.
*/
function updateCustomElementsRoot(event) {
var context = event.context,
oldRoot = context.oldRoot,
newRoot = context.newRoot,
children = oldRoot.children;
var customChildren = children.filter(isCustom);
if (customChildren.length) {
modeling.moveElements(customChildren, { x: 0, y: 0 }, newRoot);
}
}
this.postExecute('canvas.updateRoot', updateCustomElementsRoot);
}
inherits(CustomUpdater, CommandInterceptor);
CustomUpdater.$inject = [ 'eventBus', 'modeling' ];
================================================
FILE: test/integration/custom-elements/index.js
================================================
import CustomElementFactory from './CustomElementFactory';
import CustomRenderer from './CustomRenderer';
import CustomRules from './CustomRules';
import CustomUpdater from './CustomUpdater';
export default {
__init__: [
'customRenderer',
'customRules',
'customUpdater'
],
elementFactory: [ 'type', CustomElementFactory ],
customRenderer: [ 'type', CustomRenderer ],
customRules: [ 'type', CustomRules ],
customUpdater: [ 'type', CustomUpdater ]
};
================================================
FILE: test/integration/model/BpmnModdleSpec.js
================================================
import { BpmnModdle } from 'bpmn-moddle';
describe('bpmn-moddle', function() {
function parse(xml) {
var moddle = new BpmnModdle();
return moddle.fromXML(xml, 'bpmn:Definitions');
}
describe('browser support', function() {
it('should parse simple xml', function() {
var xml =
'' +
'
' +
' ' +
' ';
// when
return parse(xml).then(function(result) {
var definitions = result.rootElement;
// then
expect(definitions.id).to.equal('simple');
expect(definitions.targetNamespace).to.equal('http://bpmn.io/schema/bpmn');
expect(definitions.rootElements.length).to.equal(1);
expect(definitions.rootElements[0].id).to.equal('Process_1');
});
});
it('should parse complex xml', function() {
var xml = require('../../fixtures/bpmn/complex.bpmn');
var start = new Date().getTime();
// when
return parse(xml).then(function() {
// then
// parsing a XML document should not take too long
expect((new Date().getTime() - start)).to.be.below(1000);
});
});
});
});
================================================
FILE: test/matchers/BoundsMatchers.js
================================================
import {
pick
} from 'min-dash';
import {
getDi
} from 'lib/util/ModelUtil';
var BOUNDS_ATTRS = [ 'x', 'y', 'width', 'height' ],
POSITION_ATTRS = [ 'x', 'y' ],
DIMENSION_ATTRS = [ 'width', 'height' ];
function getBounds(s) {
if ('bounds' in s) {
s = s.bounds;
}
// TLBR object
if ('top' in s) {
return {
x: s.left,
y: s.top,
width: s.right - s.left,
height: s.bottom - s.top
};
}
// { x, y, width, height } object
else {
return pick(s, BOUNDS_ATTRS);
}
}
function getDimensions(s) {
return pick(getBounds(s), DIMENSION_ATTRS);
}
function getPosition(s) {
return pick(getBounds(s), POSITION_ATTRS);
}
export default function(chai, utils) {
var Assertion = chai.Assertion;
function inspect(obj) {
return utils.inspect(obj).replace(/\n /g, '');
}
/**
* A simple bounds matcher, that verifies an element
* has the correct { x, y, width, height }.
*
* @example
*
* expect(di.label).to.have.bounds({ x: 100, y: 100, width: 10, height: 20 });
* expect(shape).to.have.bounds({ top: 100, left: 0, right: 200, bottom: 50 });
*
* @param {Bounds|TLBR} exp
*/
Assertion.addMethod('bounds', function(exp) {
var obj = this._obj;
assertBounds(this, obj.id ? obj.id : obj, getBounds(obj), getBounds(exp));
});
/**
* A simple bounds matcher, that verifies an element
* has the correct { x, y, width, height }.
*
* @example
*
* expect(di.label).to.have.diBounds({ x: 100, y: 100, width: 10, height: 20 });
* expect(shape).to.have.diBounds({ top: 100, left: 0, right: 200, bottom: 50 });
*
* @param {Bounds|TLBR} exp
*/
Assertion.addMethod('diBounds', function(exp) {
var obj = this._obj;
var di = getDi(obj);
expect(di).to.exist;
assertBounds(this, di.id, getBounds(di), getBounds(exp));
});
/**
* A simple dimensions matcher, that verifies an element
* has the correct { width, height }.
*
* Unwraps `element.bounds` (BPMNDI) if present.
*
* @example
*
* expect(di.label).to.have.dimensions({ width: 10, height: 20 });
*
* @param {Dimensions} exp
*/
Assertion.addMethod('dimensions', function(exp) {
var obj = this._obj;
assertDimensions(this, obj.id ? obj.id : obj, getDimensions(obj), getDimensions(exp));
});
/**
* A simple dimensions matcher, that verifies an elements
* DI has the correct { width, height }.
*
* Unwraps `element.bounds` (BPMNDI) if present.
*
* @example
*
* expect(di.label).to.have.diDimensions({ width: 10, height: 20 });
*
* @param {Dimensions} exp
*/
Assertion.addMethod('diDimensions', function(exp) {
var obj = this._obj;
var di = getDi(obj);
expect(di).to.exist;
assertDimensions(this, di.id, getDimensions(di), getDimensions(exp));
});
/**
* A simple position matcher, that verifies an element
* has the correct { x, y }.
*
* Unwraps `element.bounds` (BPMNDI) if present.
*
* @example
*
* expect(taskShape).to.have.position({ x: 100, y: 150 });
*
* @param {Point} exp
*/
Assertion.addMethod('position', function(exp) {
var obj = this._obj;
assertPosition(this, obj.id ? obj.id : obj, getPosition(obj), getPosition(exp));
});
/**
* A simple position matcher, that verifies an element
* has the correct DI position { x, y }.
*
* Unwraps `element.bounds` (BPMNDI) if present.
*
* @example
*
* expect(taskShape).to.have.diPosition({ x: 100, y: 150 });
*
* @param {Point} exp
*/
Assertion.addMethod('diPosition', function(exp) {
var obj = this._obj;
var di = getDi(obj);
expect(di).to.exist;
assertPosition(this, di.id, getPosition(di), getPosition(exp));
});
// helpers ////////////////
function assertBounds(self, desc, bounds, expectedBounds) {
var matches = utils.eql(bounds, expectedBounds);
var boundsStr = inspect(bounds),
expectedBoundsStr = inspect(expectedBounds);
var theAssert = new Assertion(bounds);
// transfer flags
utils.transferFlags(self, theAssert, false);
theAssert.assert(
matches,
'expected <' + desc + '> bounds ' +
'to equal \n ' + expectedBoundsStr +
'\nbut got\n ' + boundsStr,
'expected <' + desc + '> bounds ' +
'not to equal \n ' + expectedBoundsStr,
expectedBounds
);
}
function assertDimensions(self, desc, dimensions, expectedDimensions) {
var matches = utils.eql(dimensions, expectedDimensions);
var dimensionsStr = inspect(dimensions),
expectedDimensionsStr = inspect(expectedDimensions);
var theAssert = new Assertion(dimensions);
// transfer flags
utils.transferFlags(self, theAssert, false);
theAssert.assert(
matches,
'expected <' + desc + '> dimensions ' +
'to equal \n ' + expectedDimensionsStr +
'\nbut got\n ' + dimensionsStr,
'expected <' + desc + '> dimensions ' +
'not to equal \n ' + expectedDimensionsStr,
expectedDimensions
);
}
function assertPosition(self, desc, position, expectedPosition) {
var matches = utils.eql(position, expectedPosition);
var positionStr = inspect(position),
expectedPositionStr = inspect(expectedPosition);
var theAssert = new Assertion(position);
// transfer flags
utils.transferFlags(self, theAssert, false);
theAssert.assert(
matches,
'expected <' + desc + '> position ' +
'to equal \n ' + expectedPositionStr +
'\nbut got\n ' + positionStr,
'expected <' + desc + '> position ' +
'not to equal \n ' + expectedPositionStr,
expectedPosition
);
}
}
================================================
FILE: test/matchers/ConnectionMatchers.js
================================================
import {
pick
} from 'min-dash';
import {
getDi
} from 'lib/util/ModelUtil';
var POSITION_ATTRS = [ 'x', 'y' ];
function getPoint(point) {
return pick(point, POSITION_ATTRS);
}
function getPoints(waypoints) {
return waypoints.map(getPoint);
}
export default function(chai, utils) {
var Assertion = chai.Assertion;
function inspect(obj) {
return utils.inspect(obj).replace(/\n /g, '');
}
/**
* A simple waypoints matcher, that verifies a connection
* consists of the correct connection points.
*
* Does not take the original docking into account.
*
* @example
*
* expect(connection).to.have.waypoints([ { x: 100, y: 100 }, { x: 0, y: 0 } ]);
*
* @param {Connection|Array
} exp
*/
Assertion.addMethod('waypoints', function(exp) {
var obj = this._obj;
expect(obj).to.have.property('waypoints');
assertWaypoints(this, obj.id + '#waypoints', getPoints(obj.waypoints), getPoints(exp));
});
/**
* A simple waypoints matcher, that verifies a connection
* consists of the correct DI waypoints.
*
* Does not take the original docking into account.
*
* @example
*
* expect(connection).to.have.diWaypoints([ { x: 100, y: 100 }, { x: 0, y: 0 } ]);
*
* @param {Connection|Point[]} exp
*/
Assertion.addMethod('diWaypoints', function(exp) {
var obj = this._obj;
var di = getDi(obj);
expect(di).to.exist;
expect(di).to.have.property('waypoint');
assertWaypoints(this, di + '#waypoint', getPoints(di.waypoint), getPoints(exp));
});
/**
* A simple waypoints matcher, that verifies a connection
* has the given start docking.
*
* @example
*
* expect(connection).to.have.startDocking({ x: 100, y: 100 });
*
* @param {Point} exp
*/
Assertion.addMethod('startDocking', function(exp) {
var obj = this._obj;
var startPoint = obj.waypoints[0],
startDocking = startPoint && startPoint.original;
var matches = utils.eql(startDocking, exp);
var startDockingStr = inspect(startDocking),
expectedStartDockingStr = inspect(exp);
var theAssert = new Assertion(startDocking);
// transfer negate status
utils.transferFlags(this, theAssert, false);
theAssert.assert(
matches,
'expected <' + obj.id + '> to have startDocking ' +
expectedStartDockingStr + ' but got ' + startDockingStr
);
});
/**
* A simple waypoints matcher, that verifies a connection
* has the given start docking.
*
* @example
*
* expect(connection).to.have.endDocking({ x: 100, y: 100 });
*
* @param {Point} exp
*/
Assertion.addMethod('endDocking', function(exp) {
var obj = this._obj;
var endPoint = obj.waypoints[obj.waypoints.length - 1],
endDocking = endPoint && endPoint.original;
var matches = utils.eql(endDocking, exp);
var endDockingStr = inspect(endDocking),
expectedEndDockingStr = inspect(exp);
var theAssert = new Assertion(endDocking);
// transfer negate status
utils.transferFlags(this, theAssert, false);
theAssert.assert(
matches,
'expected <' + obj.id + '> to have endDocking ' +
expectedEndDockingStr + ' but got ' + endDockingStr
);
});
// helpers ////////////////
function assertWaypoints(self, desc, waypoints, expectedWaypoints) {
var matches = utils.eql(waypoints, expectedWaypoints);
var waypointsStr = inspect(waypoints),
expectedWaypointsStr = inspect(expectedWaypoints);
var theAssert = new Assertion(waypoints);
// transfer negate status
utils.transferFlags(self, theAssert, false);
theAssert.assert(
matches,
'expected <' + desc + '> ' +
'to equal \n ' + expectedWaypointsStr +
'\nbut got\n ' + waypointsStr,
'expected <' + desc + '> ' +
'not to equal \n ' + expectedWaypoints,
expectedWaypoints
);
}
}
================================================
FILE: test/matchers/JSONMatcher.js
================================================
export default function(chai, utils) {
var Assertion = chai.Assertion;
Assertion.addMethod('jsonEqual', function(comparison, filter) {
var actual = JSON.stringify(this._obj, filter, ' ');
var expected = JSON.stringify(comparison, filter, ' ');
this.assert(
actual == expected,
'expected #{this} to json equal #{exp} but got #{act}',
'expected #{this} not to json equal #{exp}',
expected, // expected
actual, // actual
true // show diff
);
});
}
================================================
FILE: test/spec/BaseModelerSpec.js
================================================
import BaseModeler from 'lib/BaseModeler';
import BaseViewer from 'lib/BaseViewer';
import inherits from 'inherits-browser';
const spy = sinon.spy;
describe('BaseModeler', function() {
it('should instantiate', function() {
// when
var instance = new BaseModeler();
// then
expect(instance.importXML).to.exist;
expect(instance.saveXML).to.exist;
expect(instance instanceof BaseModeler).to.be.true;
expect(instance instanceof BaseViewer).to.be.true;
});
describe('#getModule', function() {
it('should allow override with context', function() {
// given
const options = {
__foo: 1,
some: {
other: {
thing: 'yes'
}
}
};
function SpecialModeler(options) {
this.getModules = spy(function(localOptions) {
expect(localOptions, 'options are passed').to.exist;
expect(localOptions).to.include(options);
return BaseModeler.prototype.getModules.call(this, localOptions);
});
BaseModeler.call(this, options);
}
inherits(SpecialModeler, BaseModeler);
// when
var instance = new SpecialModeler(options);
// then
expect(instance.getModules).to.have.been.calledOnce;
expect(instance instanceof SpecialModeler).to.be.true;
expect(instance instanceof BaseModeler).to.be.true;
expect(instance instanceof BaseViewer).to.be.true;
});
});
});
================================================
FILE: test/spec/BaseViewerSpec.js
================================================
import BaseViewer from 'lib/BaseViewer';
import inherits from 'inherits-browser';
const spy = sinon.spy;
describe('BaseViewer', function() {
it('should instantiate', function() {
// when
var instance = new BaseViewer();
// then
expect(instance.importXML).to.exist;
expect(instance.saveXML).to.exist;
expect(instance instanceof BaseViewer).to.be.true;
});
describe('#getModule', function() {
it('should allow override with context', function() {
// given
const options = {
__foo: 1,
some: {
other: {
thing: 'yes'
}
}
};
function SpecialViewer(options) {
this.getModules = spy(function(localOptions) {
expect(localOptions, 'options are passed').to.exist;
expect(localOptions).to.include(options);
return BaseViewer.prototype.getModules.call(this, localOptions);
});
BaseViewer.call(this, options);
}
inherits(SpecialViewer, BaseViewer);
// when
var instance = new SpecialViewer(options);
// then
expect(instance.getModules).to.have.been.calledOnce;
expect(instance instanceof SpecialViewer).to.be.true;
expect(instance instanceof BaseViewer).to.be.true;
});
});
});
================================================
FILE: test/spec/Modeler.copy-paste.a.bpmn
================================================
================================================
FILE: test/spec/Modeler.copy-paste.b.bpmn
================================================
================================================
FILE: test/spec/Modeler.copy-paste.complex.bpmn
================================================
Text_Annotation
Flow_1
${ foobar }
Flow_1
Flow_2
DataStoreReference
Flow_2
DataObjectReference
Flow_3
FlowDefault
FlowConditional
Flow_7
Flow_7
Flow_8
Flow_8
Flow_3
FlowDefault
Flow_4
Flow_6
Flow_4
Flow_5
foo()
Flow_5
Flow_6
bar()
FlowConditional
waat()
Conditions, Default Flow and friends
================================================
FILE: test/spec/Modeler.copy-paste.empty.bpmn
================================================
================================================
FILE: test/spec/ModelerSpec.js
================================================
import { expectToBeAccessible } from '@bpmn-io/a11y';
import Modeler from 'lib/Modeler';
import Viewer from 'lib/Viewer';
import NavigatedViewer from 'lib/NavigatedViewer';
import { 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 {
setBpmnJS,
clearBpmnJS,
collectTranslations,
enableLogging
} from 'test/TestHelper';
import {
pick,
find
} from 'min-dash';
import { getDi } from 'lib/util/ModelUtil';
var singleStart = window.__env__ && window.__env__.SINGLE_START === 'modeler';
describe('Modeler', function() {
var container;
var modeler;
beforeEach(function() {
container = TestContainer.get(this);
});
function createModeler(xml) {
clearBpmnJS();
modeler = new Modeler({
container: container,
});
setBpmnJS(modeler);
enableLogging(modeler, singleStart);
return modeler.importXML(xml).then(function(result) {
return { error: null, warnings: result.warnings, modeler: modeler };
}).catch(function(err) {
return { error: err, warnings: err.warnings, modeler: modeler };
});
}
(singleStart ? it.only : it)('should import simple process', function() {
var xml = require('../fixtures/bpmn/simple.bpmn');
return createModeler(xml).then(function(result) {
expect(result.error).not.to.exist;
});
});
it('should import collaboration', function() {
var xml = require('../fixtures/bpmn/collaboration-message-flows.bpmn');
return createModeler(xml).then(function(result) {
expect(result.error).not.to.exist;
});
});
it('should import nested lanes', function() {
var xml = require('./features/modeling/lanes/lanes.bpmn');
return createModeler(xml).then(function(result) {
expect(result.error).not.to.exist;
});
});
it('should import vertical collaboration', function() {
var xml = require('../fixtures/bpmn/collaboration-vertical.bpmn');
return createModeler(xml).then(function(result) {
expect(result.error).not.to.exist;
});
});
it('should import ioSpecification', function() {
var xml = require('./features/modeling/input-output/DataInputOutput.bpmn');
return createModeler(xml).then(function(result) {
expect(result.error).not.to.exist;
});
});
it.skip('should import complex', function() {
var xml = require('../fixtures/bpmn/complex.bpmn');
return createModeler(xml).then(function(result) {
expect(result.error).not.to.exist;
});
});
it('should not import empty definitions', function() {
var xml = require('../fixtures/bpmn/empty-definitions.bpmn');
// given
return createModeler(xml).then(function(result) {
var modeler = result.modeler;
// when
return modeler.importXML(xml);
}).catch(function(err) {
// then
expect(err.message).to.equal('no diagram to display');
});
});
it('should re-import simple process', function() {
var xml = require('../fixtures/bpmn/simple.bpmn');
// given
return createModeler(xml).then(function(result) {
var modeler = result.modeler;
// when
// mimic re-import of same diagram
return modeler.importXML(xml);
}).then(function(result) {
var warnings = result.warnings;
// then
expect(warnings).to.be.empty;
});
});
it('should switch between diagrams', function() {
var multipleXML = require('../fixtures/bpmn/multiple-diagrams.bpmn');
// given
return createModeler(multipleXML).then(function(result) {
var modeler = result.modeler;
var err = result.error;
if (err) {
throw err;
}
// when
return modeler.open('BpmnDiagram_2');
}).then(function(result) {
var warnings = result.warnings;
// then
expect(warnings).to.be.empty;
});
});
!collectTranslations && describe('translate support', function() {
var xml = require('../fixtures/bpmn/simple.bpmn');
it('should allow translation of multi-lingual strings', function() {
return createModeler(xml).then(function(result) {
var modeler = result.modeler;
var err = result.error;
if (err) {
throw err;
}
// given
var translate = modeler.get('translate');
// assume
expect(translate).to.exist;
// when
var interpolatedString = translate('HELLO {you}!', { you: 'WALT' });
// then
expect(interpolatedString).to.eql('HELLO WALT!');
});
});
});
it('should include Outline module by default', function() {
// given
var modeler = new Modeler();
// when
var outline = modeler.get('outline', false);
// then
expect(outline).to.exist;
});
describe('overlay support', function() {
it('should allow to add overlays', function() {
var xml = require('../fixtures/bpmn/simple.bpmn');
return createModeler(xml).then(function(result) {
var modeler = result.modeler;
var err = result.error;
if (err) {
throw err;
}
// given
var overlays = modeler.get('overlays'),
elementRegistry = modeler.get('elementRegistry');
// assume
expect(overlays).to.exist;
expect(elementRegistry).to.exist;
// when
overlays.add('SubProcess_1', 'badge', {
position: {
bottom: 0,
right: 0
},
html: 'YUP GREAT STUFF!
'
});
overlays.add('StartEvent_1', 'badge', {
position: {
top: 0,
left: 0
},
html: 'YUP GREAT STUFF!
'
});
// then
expect(overlays.get({ element: 'SubProcess_1', type: 'badge' })).to.have.length(1);
expect(overlays.get({ element: 'StartEvent_1', type: 'badge' })).to.have.length(1);
});
});
});
describe('editor actions support', function() {
it('should ship all actions', function() {
// given
var expectedActions = [
'undo',
'redo',
'copy',
'duplicate',
'paste',
'cut',
'stepZoom',
'zoom',
'removeSelection',
'moveCanvas',
'moveSelection',
'selectElements',
'spaceTool',
'lassoTool',
'handTool',
'globalConnectTool',
'distributeElements',
'alignElements',
'setColor',
'directEditing',
'find',
'moveToOrigin',
'replaceElement'
];
var modeler = new Modeler();
// when
var editorActions = modeler.get('editorActions');
// then
var actualActions = editorActions.getActions();
expect(actualActions).to.eql(expectedActions);
});
});
describe('bendpoint editing support', function() {
it('should allow to edit bendpoints', function() {
var xml = require('../fixtures/bpmn/simple.bpmn');
return createModeler(xml).then(function(result) {
var modeler = result.modeler;
var err = result.error;
if (err) {
throw err;
}
// given
var bendpointMove = modeler.get('bendpointMove'),
dragging = modeler.get('dragging'),
elementRegistry = modeler.get('elementRegistry');
// assume
expect(bendpointMove).to.exist;
// when
bendpointMove.start(
createCanvasEvent({ x: 0, y: 0 }),
elementRegistry.get('SequenceFlow_1'),
1
);
dragging.move(createCanvasEvent({ x: 200, y: 200 }));
});
});
});
describe('color support', function() {
it('should allow color changes', function() {
var xml = require('../fixtures/bpmn/simple.bpmn');
return createModeler(xml).then(function(result) {
var modeler = result.modeler;
// given
var modeling = modeler.get('modeling'),
elementRegistry = modeler.get('elementRegistry'),
eventShape = elementRegistry.get('StartEvent_2');
// when
// set color for StartEvent_2
modeling.setColor(eventShape, {
fill: 'FUCHSIA',
stroke: 'YELLOW'
});
// test saving process to get XML
return modeler.saveXML({ format: true });
}).then(function(result) {
var xml = result.xml;
expect(xml).not.to.contain('di="[object Object]"');
});
});
});
describe('configuration', function() {
// given
var xml = require('../fixtures/bpmn/simple.bpmn');
it('should configure Canvas', function() {
// given
var modeler = new Modeler({
container: container,
canvas: {
deferUpdate: true
}
});
// when
return modeler.importXML(xml).then(function() {
var canvasConfig = modeler.get('config.canvas');
// then
expect(canvasConfig.deferUpdate).to.be.true;
});
});
});
describe('ids', function() {
it('should provide ids with moddle', function() {
// given
var modeler = new Modeler({ container: container });
// when
var moddle = modeler.get('moddle');
// then
expect(moddle.ids).to.exist;
});
it('should populate ids on import', function() {
// given
var xml = require('../fixtures/bpmn/simple.bpmn');
var modeler = new Modeler({ container: container });
var moddle = modeler.get('moddle');
var elementRegistry = modeler.get('elementRegistry');
// when
return modeler.importXML(xml).then(function() {
var subProcess = elementRegistry.get('SubProcess_1').businessObject;
var bpmnEdge = getDi(elementRegistry.get('SequenceFlow_3'));
// then
expect(moddle.ids.assigned('SubProcess_1')).to.eql(subProcess);
expect(moddle.ids.assigned('BPMNEdge_SequenceFlow_3')).to.eql(bpmnEdge);
});
});
it('should clear ids before re-import', function() {
// given
var someXML = require('../fixtures/bpmn/simple.bpmn'),
otherXML = require('../fixtures/bpmn/basic.bpmn');
var modeler = new Modeler({ container: container });
var moddle = modeler.get('moddle');
var elementRegistry = modeler.get('elementRegistry');
// when
return modeler.importXML(someXML).then(function() {
return modeler.importXML(otherXML);
}).then(function() {
var task = elementRegistry.get('Task_1').businessObject;
// then
// not in other.bpmn
expect(moddle.ids.assigned('SubProcess_1')).to.be.false;
// in other.bpmn
expect(moddle.ids.assigned('Task_1')).to.eql(task);
});
});
});
it('should handle errors', function() {
var xml = 'invalid stuff';
var modeler = new Modeler({ container: container });
return modeler.importXML(xml).catch(function(err) {
expect(err).to.exist;
});
});
it('should error when accessing from businessObject', function() {
var xml = require('../fixtures/bpmn/simple.bpmn');
var modeler = new Modeler({ container: container });
return modeler.importXML(xml).then(function() {
// given
var elementRegistry = modeler.get('elementRegistry'),
shape = elementRegistry.get('Task_1');
// then
expect(shape.di).to.exist;
expect(function() {
shape.businessObject.di;
}).to.throw(/The di is available through the diagram element only./);
});
});
it('should create new diagram', function() {
var modeler = new Modeler({ container: container });
return modeler.createDiagram();
});
describe('dependency injection', function() {
it('should provide self as ', function() {
var xml = require('../fixtures/bpmn/simple.bpmn');
return createModeler(xml).then(function(result) {
var modeler = result.modeler;
var err = result.error;
if (err) {
throw err;
}
expect(modeler.get('bpmnjs')).to.equal(modeler);
});
});
it('should allow Diagram#get before import', function() {
// when
var modeler = new Modeler({ container: container });
// then
var eventBus = modeler.get('eventBus');
expect(eventBus).to.exist;
});
it('should keep references to services across re-import', function() {
// given
var someXML = require('../fixtures/bpmn/simple.bpmn'),
otherXML = require('../fixtures/bpmn/basic.bpmn');
var modeler = new Modeler({ container: container });
var eventBus = modeler.get('eventBus'),
canvas = modeler.get('canvas');
// when
return modeler.importXML(someXML).then(function() {
// then
expect(modeler.get('canvas')).to.equal(canvas);
expect(modeler.get('eventBus')).to.equal(eventBus);
return modeler.importXML(otherXML);
}).then(function() {
// then
expect(modeler.get('canvas')).to.equal(canvas);
expect(modeler.get('eventBus')).to.equal(eventBus);
});
});
it('should inject mandatory modules', function() {
// given
var xml = require('../fixtures/bpmn/simple.bpmn');
// when
return createModeler(xml).then(function(result) {
var modeler = result.modeler;
var err = result.error;
// then
if (err) {
throw err;
}
expect(modeler.get('alignElements')).to.exist;
expect(modeler.get('autoPlace')).to.exist;
expect(modeler.get('bpmnAutoResize')).to.exist;
expect(modeler.get('autoScroll')).to.exist;
expect(modeler.get('bendpoints')).to.exist;
expect(modeler.get('bpmnCopyPaste')).to.exist;
expect(modeler.get('bpmnSearch')).to.exist;
expect(modeler.get('contextPad')).to.exist;
expect(modeler.get('copyPaste')).to.exist;
expect(modeler.get('alignElements')).to.exist;
expect(modeler.get('distributeElements')).to.exist;
expect(modeler.get('editorActions')).to.exist;
expect(modeler.get('keyboard')).to.exist;
expect(modeler.get('keyboardMoveSelection')).to.exist;
expect(modeler.get('labelEditingProvider')).to.exist;
expect(modeler.get('labelLink')).to.exist;
expect(modeler.get('modeling')).to.exist;
expect(modeler.get('move')).to.exist;
expect(modeler.get('paletteProvider')).to.exist;
expect(modeler.get('resize')).to.exist;
expect(modeler.get('snapping')).to.exist;
});
});
});
describe('copy and paste', function() {
var m1, m2;
afterEach(function() {
if (m1) {
m1.destroy();
}
if (m2) {
m2.destroy();
}
});
function isNamedA(element) {
return element.type !== 'label' && element.businessObject.name === 'A';
}
it('should share Clipboard', function() {
var aXML = require('./Modeler.copy-paste.a.bpmn');
var bXML = require('./Modeler.copy-paste.b.bpmn');
var clipboardModule = {
'clipboard': [ 'value', new Clipboard() ]
};
m2 = new Modeler({
container: container,
additionalModules: [
clipboardModule
]
});
m1 = new Modeler({
container: container,
additionalModules: [
clipboardModule
]
});
return Promise.all([
m1.importXML(aXML),
m2.importXML(bXML)
]).then(function() {
// given
// copy element from m1
m1.invoke(function(selection, elementRegistry, editorActions) {
selection.select(elementRegistry.get('A'));
editorActions.trigger('copy');
});
// TODO(nikku): needed for our canvas utilities to work
setBpmnJS(m2);
m2.invoke(function(dragging, editorActions, elementRegistry) {
var processElement = elementRegistry.get('Process_1');
// when
// paste element to m2, first try
editorActions.trigger('paste');
dragging.move(createCanvasEvent({ x: 150, y: 150 }));
dragging.move(createCanvasEvent({ x: 170, y: 150 }));
dragging.hover({ element: processElement });
dragging.end();
// then
expect(elementRegistry.get('A')).to.exist;
expect(elementRegistry.filter(isNamedA)).to.have.lengthOf(1);
// but when
// paste element to m2, second try
editorActions.trigger('paste');
dragging.move(createCanvasEvent({ x: 150, y: 150 }));
dragging.move(createCanvasEvent({ x: 300, y: 150 }));
dragging.hover({ element: processElement });
dragging.end();
// then
expect(elementRegistry.filter(isNamedA)).to.have.lengthOf(2);
});
});
});
it('should copy + paste via serialized tree', function() {
this.timeout(3000);
var aXML = require('./Modeler.copy-paste.complex.bpmn');
var bXML = require('./Modeler.copy-paste.empty.bpmn');
m2 = new Modeler({
container: container
});
m1 = new Modeler({
container: container
});
return Promise.all([
m1.importXML(aXML),
m2.importXML(bXML)
]).then(function() {
// given
// copy all from m1
var serializedTree = m1.invoke(function(clipboard, editorActions) {
editorActions.trigger('selectElements');
editorActions.trigger('copy');
return JSON.stringify(clipboard.get());
});
// assume
expect(serializedTree).to.exist;
// TODO(nikku): needed for our canvas utilities to work
setBpmnJS(m2);
m2.invoke(function(
moddle, clipboard, dragging,
editorActions, elementRegistry,
bpmnjs) {
var definitions = bpmnjs.getDefinitions();
var processElement = elementRegistry.get('Process_1');
// when
// deserialize tree
var tree = JSON.parse(serializedTree, createReviver(moddle));
// set to clipboard
clipboard.set(tree);
// paste all to m2
editorActions.trigger('paste');
dragging.move(createCanvasEvent({ x: 150, y: 150 }));
dragging.move(createCanvasEvent({ x: 170, y: 150 }));
dragging.hover({ element: processElement });
dragging.end();
// then
// elements exist with original IDs
var expectedIds = [
'P1',
'P2',
'DataStoreReference',
'DataObjectReference',
'DataOutputAssociation',
'Say_Hello_Error',
'Group_No_Name',
'Group_With_Name',
'Collapsed_Sub',
'Sub_Process_Expanded_Nested',
'FlowDefault',
'FlowConditional',
'Text_Annotation',
'Association'
];
expectedIds.forEach(function(id) {
expect(elementRegistry.get(id), 'element <' + id + '>').to.exist;
});
// global elements exist
var expectedGlobals = [
[ 'Error_1', { name: 'SomeError', errorCode: '100' } ],
[ 'Escalation_1', { name: 'Escalation' } ],
[ 'Category_1', { } ]
];
var globals = [
'bpmn:Error',
'bpmn:Category',
'bpmn:Escalation',
'bpmn:Signal',
'bpmn:Message'
];
var globalElements = definitions.get('rootElements').filter(function(element) {
return isAny(element, globals);
});
// expect
expect(globalElements).to.have.length(expectedGlobals.length);
expectedGlobals.forEach(function(expected) {
var id = expected[0];
var attrs = expected[1];
var actualGlobal = find(globalElements, function(el) {
return el.id === id;
});
expect(actualGlobal, 'global <' + id + '>').to.exist;
var actualAttrs = pick(actualGlobal, Object.keys(attrs));
expect(actualAttrs, 'global <' + id + '> attrs').to.eql(attrs);
});
});
});
});
it.skip('should copy + delete + paste');
});
describe('drill down', function() {
function verifyDrilldown() {
var drilldown = container.querySelector('.bjs-drilldown');
var breadcrumbs = container.querySelector('.bjs-breadcrumbs');
var djsContainer = container.querySelector('.djs-container');
// assume
expect(drilldown).to.exist;
expect(breadcrumbs).to.exist;
expect(djsContainer.classList.contains('bjs-breadcrumbs-shown')).to.be.false;
// when
drilldown.click();
// then
expect(djsContainer.classList.contains('bjs-breadcrumbs-shown')).to.be.true;
}
it('should allow drill down into collapsed sub-process', function() {
var xml = require('../fixtures/bpmn/collapsed-sub-process.bpmn');
return createModeler(xml).then(verifyDrilldown);
});
it('should allow drill down into collapsed sub-process after viewer.open', function() {
var xml = require('../fixtures/bpmn/collapsed-sub-process.bpmn');
return createModeler(xml)
.then(function() {
return modeler.open('rootProcess_diagram');
})
.then(verifyDrilldown);
});
it('should allow drill down into legacy collapsed sub-process', function() {
var xml = require('../fixtures/bpmn/collapsed-sub-process-legacy.bpmn');
return createModeler(xml).then(verifyDrilldown);
});
it('should allow creation of groups in collapsed subprocesses', function() {
var xml = require('../fixtures/bpmn/collapsed-sub-process.bpmn');
return createModeler(xml).then(function() {
// given
var elementRegistry = modeler.get('elementRegistry'),
elementFactory = modeler.get('elementFactory'),
modeling = modeler.get('modeling');
var collapsedProcessPlane = elementRegistry.get('collapsedProcess_plane'),
groupElement = elementFactory.createShape({ type: 'bpmn:Group' });
// when
var group = modeling.createShape(groupElement, { x: 100, y: 100 }, collapsedProcessPlane);
// then
expect(group).to.exist;
expect(group.parent).to.equal(collapsedProcessPlane);
});
});
});
it('should expose Viewer and NavigatedViewer', function() {
expect(Modeler.Viewer).to.equal(Viewer);
expect(Modeler.NavigatedViewer).to.equal(NavigatedViewer);
});
describe('accessibility', function() {
it('should report no issues', async function() {
// given
const xml = require('../fixtures/bpmn/simple.bpmn');
await createModeler(xml);
// then
await expectToBeAccessible(container);
});
});
});
// helpers //////////////
/**
* A factory function that returns a reviver to be
* used with JSON#parse to reinstantiate moddle instances.
*
* @param { Moddle } moddle
*
* @return { (key: string, object: any) => any|null }
*/
function createReviver(moddle) {
var elCache = {};
/**
* The actual reviewer that creates model instances
* for elements with a $type attribute.
*
* Elements with ids will be re-used, if already
* created.
*
* @param {string} key
* @param {any} object
*
* @return {any|null} actual element
*/
return function(key, object) {
if (typeof object === 'object' && typeof object.$type === 'string') {
var objectId = object.id;
if (objectId && elCache[objectId]) {
return elCache[objectId];
}
var type = object.$type;
var attrs = Object.assign({}, object);
delete attrs.$type;
var newEl = moddle.create(type, attrs);
if (objectId) {
elCache[objectId] = newEl;
}
return newEl;
}
return object;
};
}
================================================
FILE: test/spec/NavigatedViewerSpec.js
================================================
import { expectToBeAccessible } from '@bpmn-io/a11y';
import NavigatedViewer from 'lib/NavigatedViewer';
import EditorActionsModule from 'lib/features/editor-actions';
import TestContainer from 'mocha-test-container-support';
import {
createViewer
} from 'test/TestHelper';
var singleStart = window.__env__ && window.__env__.SINGLE_START === 'navigated-viewer';
describe('NavigatedViewer', function() {
var container;
beforeEach(function() {
container = TestContainer.get(this);
});
(singleStart ? it.only : it)('should import simple process', function() {
var xml = require('../fixtures/bpmn/simple.bpmn');
return createViewer(container, NavigatedViewer, xml).then(function(result) {
expect(result.error).not.to.exist;
});
});
describe('editor actions support', function() {
it('should not ship per default', function() {
// given
var navigatedViewer = new NavigatedViewer();
// when
var editorActions = navigatedViewer.get('editorActions', false);
// then
expect(editorActions).not.to.exist;
});
it('should ship non-modeling actions if included', function() {
// given
var expectedActions = [
'stepZoom',
'zoom',
'moveCanvas',
'selectElements'
];
var navigatedViewer = new NavigatedViewer({
additionalModules: [
EditorActionsModule
]
});
// when
var editorActions = navigatedViewer.get('editorActions');
// then
var actualActions = editorActions.getActions();
expect(actualActions).to.eql(expectedActions);
});
});
describe('navigation features', function() {
var xml = require('../fixtures/bpmn/simple.bpmn');
it('should include zoomScroll', function() {
return createViewer(container, NavigatedViewer, xml).then(function(result) {
var viewer = result.viewer;
var err = result.error;
expect(err).not.to.exist;
expect(viewer.get('zoomScroll')).to.exist;
});
});
it('should include moveCanvas', function() {
return createViewer(container, NavigatedViewer, xml).then(function(result) {
var viewer = result.viewer;
var err = result.error;
expect(err).not.to.exist;
expect(viewer.get('moveCanvas')).to.exist;
});
});
});
describe('accessibility', function() {
it('should report no issues', async function() {
// given
const xml = require('../fixtures/bpmn/simple.bpmn');
await createViewer(container, NavigatedViewer, xml);
// then
await expectToBeAccessible(container);
});
});
});
================================================
FILE: test/spec/ViewerSpec.js
================================================
import { expectToBeAccessible } from '@bpmn-io/a11y';
import {
query as domQuery
} from 'min-dom';
import TestContainer from 'mocha-test-container-support';
import Diagram from 'diagram-js/lib/Diagram';
import ViewerDefaultExport from '../../';
import Viewer from 'lib/Viewer';
import inherits from 'inherits-browser';
import {
createViewer
} from 'test/TestHelper';
import { getDi } from 'lib/util/ModelUtil';
var singleStart = window.__env__ && window.__env__.SINGLE_START === 'viewer';
describe('Viewer', function() {
var container;
beforeEach(function() {
container = TestContainer.get(this);
});
(singleStart ? it.only : it)('should import simple process', function() {
var xml = require('../fixtures/bpmn/simple.bpmn');
// when
return createViewer(container, Viewer, xml).then(function(result) {
var err = result.error;
var warnings = result.warnings;
var viewer = result.viewer;
// then
expect(err).not.to.exist;
expect(warnings).to.be.empty;
var definitions = viewer.getDefinitions();
expect(definitions).to.exist;
expect(definitions).to.eql(viewer._definitions);
});
});
it('should re-import simple process', function() {
var xml = require('../fixtures/bpmn/simple.bpmn');
// given
return createViewer(container, Viewer, xml).then(function(result) {
var viewer = result.viewer;
// when
// mimic re-import of same diagram
return viewer.importXML(xml).then(function(result) {
// then
expect(result.warnings).to.be.empty;
});
});
});
it('should be instance of Diagram', function() {
// when
var viewer = new Viewer({ container: container });
// then
expect(viewer).to.be.instanceof(Diagram);
});
it('should not include Outline module by default', function() {
// given
var viewer = new Viewer();
// when
var outline = viewer.get('outline', false);
// then
expect(outline).not.to.exist;
});
describe('overlay support', function() {
it('should allow to add overlays', function() {
var xml = require('../fixtures/bpmn/simple.bpmn');
return createViewer(container, Viewer, xml).then(function(result) {
var err = result.error;
var viewer = result.viewer;
expect(err).not.to.exist;
// when
var overlays = viewer.get('overlays'),
elementRegistry = viewer.get('elementRegistry');
// then
expect(overlays).to.exist;
expect(elementRegistry).to.exist;
// when
overlays.add('SubProcess_1', {
position: {
bottom: 0,
right: 0
},
html: 'YUP GREAT STUFF!
'
});
// then
expect(overlays.get({ element: 'SubProcess_1' }).length).to.equal(1);
});
});
});
describe('editor actions support', function() {
it('should not ship per default', function() {
// given
var viewer = new Viewer();
// when
var editorActions = viewer.get('editorActions', false);
// then
expect(editorActions).not.to.exist;
});
});
describe('error handling', function() {
function expectMessage(e, expectedMessage) {
expect(e).to.exist;
if (expectedMessage instanceof RegExp) {
expect(e.message).to.match(expectedMessage);
} else {
expect(e.message).to.equal(expectedMessage);
}
}
function expectWarnings(warnings, expected) {
expect(warnings.length).to.equal(expected.length);
warnings.forEach(function(w, idx) {
expectMessage(w, expected[idx]);
});
}
it('should handle non-bpmn input', function() {
var xml = 'invalid stuff';
return createViewer(container, Viewer, xml).then(function(result) {
var err = result.error;
expect(err).to.exist;
expectMessage(err, /missing start tag/);
});
});
it('should handle invalid BPMNPlane#bpmnElement', function() {
var xml = require('../fixtures/bpmn/error/di-plane-no-bpmn-element.bpmn');
// when
return createViewer(container, Viewer, xml).then(function(result) {
var err = result.error;
var warnings = result.warnings;
// then
expect(err).not.to.exist;
expectWarnings(warnings, [
'unresolved reference ',
'no bpmnElement referenced in ',
'correcting missing bpmnElement ' +
'on ' +
'to '
]);
});
});
it('should handle invalid namespaced element', function() {
var xml = require('../fixtures/bpmn/error/categoryValue.bpmn');
// when
return createViewer(container, Viewer, xml).then(function(result) {
var err = result.error;
var warnings = result.warnings;
// then
expect(err).not.to.exist;
expectWarnings(warnings, [
/unparsable content detected/,
'unresolved reference '
]);
});
});
it('should handle missing namespace', function() {
var xml = require('../fixtures/bpmn/error/missing-namespace.bpmn');
// when
return createViewer(container, Viewer, xml).then(function(result) {
var err = result.error;
var warnings = result.warnings;
// then
expect(err).to.exist;
expect(err.message).to.eql('failed to parse document as ');
expect(warnings).to.have.length(1);
expect(warnings[0].message).to.match(/unparsable content detected/);
});
});
it('should handle duplicate ids', function() {
var xml = require('../fixtures/bpmn/error/duplicate-ids.bpmn');
// when
return createViewer(container, Viewer, xml).then(function(result) {
var err = result.error;
var warnings = result.warnings;
// then
expect(err).not.to.exist;
expectWarnings(warnings, [
/duplicate ID /
]);
});
});
it('should throw error due to missing diagram', function() {
var xml = require('../fixtures/bpmn/empty-definitions.bpmn');
// when
return createViewer(container, Viewer, xml).then(function(result) {
var err = result.error;
// then
expect(err.message).to.eql('no diagram to display');
});
});
it('should handle missing process/collaboration', function() {
var xml = require('../fixtures/bpmn/error/no-process-collaboration.bpmn');
// when
return createViewer(container, Viewer, xml).then(function(result) {
var err = result.error;
// then
expect(err.message).to.eql('no process or collaboration to display');
});
});
it('should error when accessing from businessObject', function() {
var xml = require('../fixtures/bpmn/simple.bpmn');
return createViewer(container, Viewer, xml).then(function(result) {
// given
var viewer = result.viewer,
elementRegistry = viewer.get('elementRegistry'),
shape = elementRegistry.get('Task_1');
// then
expect(shape.di).to.exist;
expect(function() {
shape.businessObject.di;
}).to.throw(/The di is available through the diagram element only./);
});
});
});
describe('dependency injection', function() {
it('should provide self as ', function() {
var xml = require('../fixtures/bpmn/simple.bpmn');
return createViewer(container, Viewer, xml).then(function(result) {
var viewer = result.viewer;
var err = result.error;
expect(viewer.get('bpmnjs')).to.equal(viewer);
expect(err).not.to.exist;
});
});
it('should allow Diagram#get before import', function() {
// when
var viewer = new Viewer({ container: container });
// then
var eventBus = viewer.get('eventBus');
expect(eventBus).to.exist;
});
it('should keep references to services across re-import', function() {
// given
var someXML = require('../fixtures/bpmn/simple.bpmn'),
otherXML = require('../fixtures/bpmn/basic.bpmn');
var viewer = new Viewer({ container: container });
var eventBus = viewer.get('eventBus'),
canvas = viewer.get('canvas');
// when
return viewer.importXML(someXML).then(function() {
// then
expect(viewer.get('canvas')).to.equal(canvas);
expect(viewer.get('eventBus')).to.equal(eventBus);
return viewer.importXML(otherXML);
}).then(function() {
// then
expect(viewer.get('canvas')).to.equal(canvas);
expect(viewer.get('eventBus')).to.equal(eventBus);
});
});
});
describe('drill down', function() {
function verifyDrilldown(xml) {
return createViewer(container, Viewer, xml).then(function() {
var drilldown = domQuery('.bjs-drilldown', container);
var breadcrumbs = domQuery('.bjs-breadcrumbs', container);
var djsContainer = domQuery('.djs-container', container);
// assume
expect(drilldown).to.exist;
expect(breadcrumbs).to.exist;
expect(djsContainer.classList.contains('bjs-breadcrumbs-shown')).to.be.false;
// when
drilldown.click();
// then
expect(djsContainer.classList.contains('bjs-breadcrumbs-shown')).to.be.true;
});
}
it('should allow drill down into collapsed sub-process', function() {
var xml = require('../fixtures/bpmn/collapsed-sub-process.bpmn');
return verifyDrilldown(xml);
});
it('should allow drill down into legacy collapsed sub-process', function() {
var xml = require('../fixtures/bpmn/collapsed-sub-process-legacy.bpmn');
return verifyDrilldown(xml);
});
it('should allow drill down into multi-di collapsed sub-process', function() {
var xml = require('../fixtures/bpmn/multiple-nested-processes.bpmn');
return verifyDrilldown(xml);
});
});
describe('creation', function() {
var testModules = [
{ logger: [ 'type', function() { this.called = true; } ] }
];
// given
var xml = require('../fixtures/bpmn/simple.bpmn');
var viewer;
afterEach(function() {
viewer.destroy();
});
it('should override default modules', function() {
// given
viewer = new Viewer({ container: container, modules: testModules });
// when
return viewer.importXML(xml).catch(function(err) {
// then
expect(err.message).to.equal('No provider for "bpmnImporter"! (Resolving: bpmnImporter)');
});
});
it('should add module to default modules', function() {
// given
viewer = new Viewer({ container: container, additionalModules: testModules });
// when
return viewer.importXML(xml).then(function(result) {
// then
var logger = viewer.get('logger');
expect(logger.called).to.be.true;
});
});
it('should use custom size and position', function() {
// when
viewer = new Viewer({
container: container,
width: 200,
height: 100,
position: 'fixed'
});
// then
expect(viewer._container.style.position).to.equal('fixed');
expect(viewer._container.style.width).to.equal('200px');
expect(viewer._container.style.height).to.equal('100px');
});
var camundaPackage = require('../fixtures/json/model/camunda');
it('should provide custom moddle extensions', function() {
var xml = require('../fixtures/bpmn/extension/camunda.bpmn');
// given
viewer = new Viewer({
container: container,
moddleExtensions: {
camunda: camundaPackage
}
});
// when
return viewer.importXML(xml).then(function(result) {
var elementRegistry = viewer.get('elementRegistry');
var taskShape = elementRegistry.get('send'),
sendTask = taskShape.businessObject;
// then
expect(sendTask).to.exist;
var extensionElements = sendTask.extensionElements;
// receive task should be moddle extended
expect(sendTask.$instanceOf('camunda:ServiceTaskLike')).to.be.true;
// extension elements should provide typed element
expect(extensionElements).to.exist;
expect(extensionElements.values).to.exist;
expect(extensionElements.values).to.have.length(1);
expect(extensionElements.values[0].$instanceOf('camunda:InputOutput')).to.be.true;
});
});
it('should allow to add default custom moddle extensions', function() {
// given
var xml = require('../fixtures/bpmn/extension/custom.bpmn'),
additionalModdleDescriptors = {
custom: require('../fixtures/json/model/custom')
};
function CustomViewer(options) {
Viewer.call(this, options);
}
inherits(CustomViewer, Viewer);
CustomViewer.prototype._moddleExtensions = additionalModdleDescriptors;
viewer = new CustomViewer({
container: container,
moddleExtensions: {
camunda: camundaPackage
}
});
// when
return viewer.importXML(xml).then(function(result) {
var elementRegistry = viewer.get('elementRegistry');
var taskShape = elementRegistry.get('send'),
sendTask = taskShape.businessObject;
// then
expect(sendTask).to.exist;
var extensionElements = sendTask.extensionElements;
// receive task should be moddle extended
expect(sendTask.$instanceOf('camunda:ServiceTaskLike')).to.be.true;
expect(sendTask.$instanceOf('custom:ServiceTaskGroup')).to.be.true;
// extension elements should provide typed element
expect(extensionElements).to.exist;
expect(extensionElements.values.length).to.equal(2);
expect(extensionElements.values[0].$instanceOf('camunda:InputOutput')).to.be.true;
expect(extensionElements.values[1].$instanceOf('custom:CustomSendElement')).to.be.true;
});
});
it('should allow user to override default custom moddle extensions', function() {
// given
var xml = require('../fixtures/bpmn/extension/custom-override.bpmn');
var additionalModdleDescriptors = {
custom: require('../fixtures/json/model/custom')
};
var customOverride = require('../fixtures/json/model/custom-override');
function CustomViewer(options) {
Viewer.call(this, options);
}
inherits(CustomViewer, Viewer);
CustomViewer.prototype._moddleExtensions = additionalModdleDescriptors;
viewer = new CustomViewer({
container: container,
moddleExtensions: {
camunda: camundaPackage,
custom : customOverride
}
});
// when
return viewer.importXML(xml).then(function(result) {
var elementRegistry = viewer.get('elementRegistry');
var taskShape = elementRegistry.get('send'),
sendTask = taskShape.businessObject;
// then
expect(sendTask).to.exist;
var extensionElements = sendTask.extensionElements;
// receive task should be moddle extended
expect(sendTask.$instanceOf('camunda:ServiceTaskLike')).to.be.true;
expect(sendTask.$instanceOf('custom:ServiceTaskGroupOverride')).to.be.true;
// extension elements should provide typed element
expect(extensionElements).to.exist;
expect(extensionElements.values.length).to.equal(2);
expect(extensionElements.values[0].$instanceOf('camunda:InputOutput')).to.be.true;
expect(extensionElements.values[1].$instanceOf('custom:CustomSendElementOverride')).to.be.true;
});
});
});
describe('configuration', function() {
var xml = require('../fixtures/bpmn/simple.bpmn');
it('should configure Canvas', function() {
// given
var viewer = new Viewer({
container: container,
canvas: {
deferUpdate: true
}
});
// when
return viewer.importXML(xml).then(function(result) {
var canvasConfig = viewer.get('config.canvas');
// then
expect(canvasConfig.deferUpdate).to.be.true;
});
});
describe('container', function() {
it('should attach if provided', function() {
var xml = require('../fixtures/bpmn/simple.bpmn');
var viewer = new Viewer({ container: container });
return viewer.importXML(xml).then(function(result) {
expect(viewer._container.parentNode).to.equal(container);
});
});
it('should not attach if absent', function() {
var xml = require('../fixtures/bpmn/simple.bpmn');
var viewer = new Viewer();
return viewer.importXML(xml).then(function(result) {
expect(viewer._container.parentNode).to.equal(null);
});
});
});
});
describe('#importXML', function() {
it('should emit events', function() {
// given
var viewer = new Viewer({ container: container });
var xml = require('../fixtures/bpmn/simple.bpmn');
var events = [];
viewer.on([
'import.parse.start',
'import.parse.complete',
'import.render.start',
'import.render.complete',
'import.done'
], function(e) {
// log event type + event arguments
events.push([
e.type,
Object.keys(e).filter(function(key) {
return key !== 'type';
})
]);
});
// when
return viewer.importXML(xml).then(function(result) {
// then
expect(events).to.eql([
[ 'import.parse.start', [ 'xml' ] ],
[ 'import.parse.complete', [ 'error', 'definitions', 'elementsById', 'references', 'warnings' ] ],
[ 'import.render.start', [ 'definitions' ] ],
[ 'import.render.complete', [ 'error', 'warnings' ] ],
[ 'import.done', [ 'error', 'warnings' ] ]
]);
});
});
it('should work without callback', function(done) {
// given
var viewer = new Viewer({ container: container });
var xml = require('../fixtures/bpmn/simple.bpmn');
// when
viewer.importXML(xml);
// then
viewer.on('import.done', function(event) {
done();
});
});
describe('multiple BPMNDiagram elements', function() {
var multipleXML = require('../fixtures/bpmn/multiple-diagrams.bpmn');
it('should import default without bpmnDiagram specified', function() {
// when
return createViewer(container, Viewer, multipleXML).then(function(result) {
var err = result.error;
// then
expect(err).not.to.exist;
});
});
it('should import bpmnDiagram specified by id', function() {
// when
return createViewer(container, Viewer, multipleXML, 'BpmnDiagram_2').then(function(result) {
var err = result.error;
// then
expect(err).not.to.exist;
});
});
it('should handle diagram not found', function() {
// given
var xml = require('../fixtures/bpmn/multiple-diagrams.bpmn');
// when
return createViewer(container, Viewer, xml, 'Diagram_IDontExist').then(function(result) {
var err = result.error;
// then
expect(err).to.exist;
expect(err.message).to.eql('BPMNDiagram not found');
});
});
describe('without callback', function() {
it('should open default', function(done) {
// given
var viewer = new Viewer({ container: container });
// when
viewer.importXML(multipleXML);
// then
viewer.on('import.done', function(event) {
done(event.error);
});
});
it('should open specified BPMNDiagram', function(done) {
// given
var viewer = new Viewer({ container: container });
// when
viewer.importXML(multipleXML, 'BpmnDiagram_2');
// then
viewer.on('import.done', function(event) {
done(event.error);
});
});
});
});
});
describe('#importDefinitions', function() {
describe('single diagram', function() {
var xml = require('../fixtures/bpmn/simple.bpmn'),
viewer,
definitions;
beforeEach(function() {
return createViewer(container, Viewer, xml, null).then(function(result) {
var error = result.error;
var tmpViewer = result.viewer;
if (error) {
throw error;
}
definitions = tmpViewer.getDefinitions();
tmpViewer.destroy();
});
});
beforeEach(function() {
viewer = new Viewer({ container: container });
});
afterEach(function() {
viewer.destroy();
});
it('should emit events', function() {
// given
var events = [];
viewer.on([
'import.parse.start',
'import.parse.complete',
'import.render.start',
'import.render.complete',
'import.done'
], function(e) {
// log event type + event arguments
events.push([
e.type,
Object.keys(e).filter(function(key) {
return key !== 'type';
})
]);
});
// when
return viewer.importDefinitions(definitions).then(function() {
// then
expect(events).to.eql([
[ 'import.render.start', [ 'definitions' ] ],
[ 'import.render.complete', [ 'error', 'warnings' ] ]
]);
});
});
it('should work without callback', function(done) {
// given
viewer.on('import.render.complete', function(context) {
// then
done(context.error);
});
// when
viewer.importDefinitions(definitions);
});
});
describe('multiple BPMNDiagram elements', function() {
var multipleXML = require('../fixtures/bpmn/multiple-diagrams.bpmn'),
viewer,
definitions;
beforeEach(function() {
return createViewer(container, Viewer, multipleXML).then(function(result) {
var error = result.error;
var tmpViewer = result.viewer;
if (error) {
throw error;
}
definitions = tmpViewer.getDefinitions();
tmpViewer.destroy();
});
});
beforeEach(function() {
viewer = new Viewer({ container: container });
});
afterEach(function() {
viewer.destroy();
});
it('should import default without bpmnDiagram specified', function() {
// when
return viewer.importDefinitions(definitions);
});
it('should import bpmnDiagram specified by id', function() {
// when
return viewer.importDefinitions(definitions, 'BpmnDiagram_2');
});
it('should handle diagram not found', function() {
// when
return viewer.importDefinitions(definitions, 'Diagram_IDontExist').catch(function(err) {
// then
expect(err).to.exist;
expect(err.message).to.eql('BPMNDiagram not found');
});
});
describe('without callback', function() {
it('should open default', function(done) {
// given
viewer.on('import.render.complete', function(event) {
// then
done(event.error);
});
// when
viewer.importDefinitions(definitions);
});
it('should open specified BPMNDiagram', function(done) {
// given
viewer.on('import.render.complete', function(event) {
// then
done(event.error);
});
// when
viewer.importDefinitions(definitions, 'BpmnDiagram_2');
});
});
});
});
describe('#open', function() {
var multipleXMLSimple = require('../fixtures/bpmn/multiple-diagrams.bpmn'),
multipleXMLOverlappingDI = require('../fixtures/bpmn/multiple-diagrams-overlapping-di.bpmn'),
multipleXMLWithLaneSet = require('../fixtures/bpmn/multiple-diagrams-lanesets.bpmn'),
diagram1 = 'BpmnDiagram_1',
diagram2 = 'BpmnDiagram_2';
it('should open the first diagram if id was not provided', function() {
var viewer, renderedDiagram;
// when
return createViewer(container, Viewer, multipleXMLSimple, diagram1).then(function(result) {
var err = result.error;
viewer = result.viewer;
expect(err).not.to.exist;
renderedDiagram = getDi(viewer.get('canvas').getRootElement());
return viewer.open();
}).then(function() {
// then
expect(getDi(viewer.get('canvas').getRootElement())).to.equal(renderedDiagram);
});
});
it('should switch between diagrams', function() {
var viewer, definitions;
// when
return createViewer(container, Viewer, multipleXMLSimple, diagram1).then(function(result) {
var err = result.error;
var warnings = result.warnings;
viewer = result.viewer;
// then
expect(err).not.to.exist;
expect(warnings).to.be.empty;
definitions = viewer.getDefinitions();
expect(definitions).to.exist;
return viewer.open(diagram2);
}).then(function(result) {
// then
var warnings = result.warnings;
expect(warnings).to.be.empty;
expect(definitions).to.equal(viewer.getDefinitions());
var elementRegistry = viewer.get('elementRegistry');
expect(elementRegistry.get('Task_A')).to.not.exist;
expect(elementRegistry.get('Task_B')).to.exist;
});
});
it('should switch between diagrams with overlapping DI', function() {
var viewer, definitions;
// when
return createViewer(container, Viewer, multipleXMLOverlappingDI, diagram1).then(function(result) {
var err = result.error;
var warnings = result.warnings;
viewer = result.viewer;
// then
expect(err).not.to.exist;
expect(warnings).to.be.empty;
definitions = viewer.getDefinitions();
expect(definitions).to.exist;
return viewer.open(diagram2);
}).then(function(result) {
var warnings = result.warnings;
expect(warnings).to.be.empty;
expect(definitions).to.equal(viewer.getDefinitions());
});
});
it('should switch between diagrams with laneSets', function() {
var viewer, definitions;
// when
return createViewer(container, Viewer, multipleXMLWithLaneSet, diagram2).then(function(result) {
var err = result.error;
var warnings = result.warnings;
viewer = result.viewer;
// then
expect(err).not.to.exist;
expect(warnings).to.be.empty;
definitions = viewer.getDefinitions();
expect(definitions).to.exist;
return viewer.open(diagram1);
}).then(function(result) {
// then
var warnings = result.warnings;
expect(warnings).to.be.empty;
expect(definitions).to.equal(viewer.getDefinitions());
var elementRegistry = viewer.get('elementRegistry');
expect(elementRegistry.get('Task_A')).to.exist;
expect(elementRegistry.get('Task_B')).to.not.exist;
});
});
it('should complete with error if xml was not imported', function() {
// given
var viewer = new Viewer();
// when
return viewer.open().catch(function(err) {
// then
expect(err).to.exist;
expect(err.message).to.eql('no XML imported');
var definitions = viewer.getDefinitions();
expect(definitions).to.not.exist;
});
});
it('should open with error if diagram does not exist', function() {
var viewer, definitions;
// when
return createViewer(container, Viewer, multipleXMLSimple, diagram1).then(function(result) {
var err = result.error;
var warnings = result.warnings;
viewer = result.viewer;
// then
expect(err).not.to.exist;
expect(warnings).to.be.empty;
definitions = viewer.getDefinitions();
expect(definitions).to.exist;
return viewer.open('Diagram_IDontExist');
}).catch(function(err) {
// then
expect(err).to.exist;
expect(err.message).to.eql('BPMNDiagram not found');
// definitions stay the same
expect(viewer.getDefinitions()).to.eql(definitions);
});
});
it('should emit events', function() {
var viewer = new Viewer({ container: container });
var events = [];
return viewer.importXML(multipleXMLSimple, diagram1).then(function(result) {
// given
viewer.on([
'import.parse.start',
'import.parse.complete',
'import.render.start',
'import.render.complete',
'import.done'
], function(e) {
// log event type + event arguments
events.push([
e.type,
Object.keys(e).filter(function(key) {
return key !== 'type';
})
]);
});
// when
return viewer.open(diagram2);
}).then(function() {
// then
expect(events).to.eql([
[ 'import.render.start', [ 'definitions' ] ],
[ 'import.render.complete', [ 'error', 'warnings' ] ]
]);
});
});
});
describe('#saveXML', function() {
it('should export XML', function() {
// given
var xml = require('../fixtures/bpmn/simple.bpmn');
return createViewer(container, Viewer, xml).then(function(result) {
var err = result.error;
var viewer = result.viewer;
expect(err).not.to.exist;
// when
return viewer.saveXML({ format: true });
}).then(function(result) {
var xml = result.xml;
// then
expect(xml).to.contain('');
expect(xml).to.contain(' events', function() {
var xml = require('../fixtures/bpmn/simple.bpmn');
var viewer;
var events = [];
return createViewer(container, Viewer, xml).then(function(result) {
var err = result.error;
viewer = result.viewer;
expect(err).not.to.exist;
viewer.on([
'saveXML.start',
'saveXML.serialized',
'saveXML.done'
], function(e) {
// log event type + event arguments
events.push([
e.type,
Object.keys(e).filter(function(key) {
return key !== 'type';
})
]);
});
return viewer.importXML(xml);
}).then(function(result) {
// when
return viewer.saveXML();
}).then(function() {
// then
expect(events).to.eql([
[ 'saveXML.start', [ 'definitions' ] ],
[ 'saveXML.serialized', [ 'xml' ] ],
[ 'saveXML.done', [ 'xml' ] ]
]);
});
});
it('should emit on error', function() {
var xml = require('../fixtures/bpmn/simple.bpmn');
var viewer;
var events = [];
return createViewer(container, Viewer, xml).then(function(result) {
var err = result.error;
viewer = result.viewer;
expect(err).not.to.exist;
// when
viewer.on('saveXML.start', 250, function() {
throw new Error('failing pre-save listener');
});
viewer.on([
'saveXML.start',
'saveXML.serialized',
'saveXML.done'
], function(e) {
// log event type + event arguments
events.push([
e.type,
Object.keys(e).filter(function(key) {
return key !== 'type';
})
]);
});
return viewer.importXML(xml);
}).then(function(result) {
// when
return viewer.saveXML();
}).catch(function(error) {
events.push([ 'error' ]);
}).finally(function() {
// then
expect(events).to.eql([
[ 'saveXML.start', [ 'definitions' ] ],
[ 'saveXML.done', [ 'error' ] ],
[ 'error' ]
]);
});
});
it('should emit on no definitions loaded', function() {
var events = [];
var viewer = new Viewer({
container: container
});
viewer.on([
'saveXML.start',
'saveXML.serialized',
'saveXML.done'
], function(e) {
// log event type + event arguments
events.push([
e.type,
Object.keys(e).filter(function(key) {
return key !== 'type';
})
]);
});
return viewer.saveXML().catch(function(error) {
events.push([ 'error' ]);
}).finally(function() {
// then
expect(events).to.eql([
[ 'saveXML.done', [ 'error' ] ],
[ 'error' ]
]);
});
});
});
describe('#saveSVG', function() {
function currentTime() {
return new Date().getTime();
}
function validSVG(svg) {
var expectedStart = '';
var expectedEnd = '';
expect(svg.indexOf(expectedStart)).to.equal(0);
expect(svg.indexOf(expectedEnd)).to.equal(svg.length - expectedEnd.length);
// ensure correct rendering of SVG contents
expect(svg.indexOf('undefined')).to.equal(-1);
// expect header to be written only once
expect(svg.indexOf('')).to.equal(-1);
expect(svg.indexOf(', svg]
expect(svgNode.childNodes).to.have.length(3);
// no error body
expect(svgNode.body).not.to.exist;
// FIXME(nre): make matcher
return true;
}
it('should export svg', function() {
// given
var xml = require('../fixtures/bpmn/simple.bpmn');
return createViewer(container, Viewer, xml).then(function(result) {
var err = result.error;
var viewer = result.viewer;
if (err) {
throw err;
}
// when
return viewer.saveSVG();
}).then(function(result) {
var svg = result.svg;
// then
expect(validSVG(svg)).to.be.true;
});
});
it('should export huge svg', function() {
this.timeout(5000);
// given
var xml = require('../fixtures/bpmn/complex.bpmn');
return createViewer(container, Viewer, xml).then(function(result) {
var err = result.error;
var viewer = result.viewer;
if (err) {
throw err;
}
// when
return viewer.saveSVG();
}).then(function(result) {
var svg = result.svg;
var time = currentTime();
// then
expect(validSVG(svg)).to.be.true;
// no svg export should not take too long
expect(currentTime() - time).to.be.below(1000);
});
});
it('should remove outer-makers on export', function() {
// given
var xml = require('../fixtures/bpmn/simple.bpmn');
function appendTestRect(svgDoc) {
var rect = document.createElementNS(svgDoc.namespaceURI, 'rect');
rect.setAttribute('class', 'outer-bound-marker');
rect.setAttribute('width', 500);
rect.setAttribute('height', 500);
rect.setAttribute('x', 10000);
rect.setAttribute('y', 10000);
svgDoc.appendChild(rect);
}
return createViewer(container, Viewer, xml).then(function(result) {
var err = result.error;
var viewer = result.viewer;
if (err) {
throw err;
}
var svgDoc = domQuery('svg', viewer._container);
appendTestRect(svgDoc);
appendTestRect(svgDoc);
expect(domQuery('.outer-bound-marker', svgDoc)).to.exist;
// when
return viewer.saveSVG();
}).then(function(result) {
var svg = result.svg;
var svgDoc = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svgDoc.innerHTML = svg;
// then
expect(validSVG(svg)).to.be.true;
expect(domQuery('.outer-bound-marker', svgDoc)).not.to.exist;
});
});
it('should emit events', function() {
var xml = require('../fixtures/bpmn/simple.bpmn');
var viewer;
var events = [];
return createViewer(container, Viewer, xml).then(function(result) {
var err = result.error;
viewer = result.viewer;
expect(err).not.to.exist;
viewer.on([
'saveSVG.start',
'saveSVG.done'
], function(e) {
// log event type + event arguments
events.push([
e.type,
Object.keys(e).filter(function(key) {
return key !== 'type';
})
]);
});
return viewer.importXML(xml);
}).then(function() {
// when
return viewer.saveSVG();
}).then(function() {
// then
expect(events).to.eql([
[ 'saveSVG.start', [ ] ],
[ 'saveSVG.done', [ 'error', 'svg' ] ]
]);
});
});
});
describe('#on', function() {
it('should fire with given three', function() {
// given
var viewer = new Viewer({ container: container });
var xml = require('../fixtures/bpmn/simple.bpmn');
// when
viewer.on('foo', 1000, function() {
return 'bar';
}, viewer);
// then
return viewer.importXML(xml).then(function() {
var eventBus = viewer.get('eventBus');
var result = eventBus.fire('foo');
expect(result).to.equal('bar');
});
});
});
describe('#off', function() {
var xml = require('../fixtures/bpmn/simple.bpmn');
it('should remove listener permanently', function() {
// given
var viewer = new Viewer({ container: container });
var handler = function() {
return 'bar';
};
viewer.on('foo', 1000, handler);
// when
viewer.off('foo');
// then
return viewer.importXML(xml).then(function() {
var eventBus = viewer.get('eventBus');
var result = eventBus.fire('foo');
expect(result).not.to.exist;
});
});
it('should remove listener on existing diagram instance', function() {
// given
var viewer = new Viewer({ container: container });
var handler = function() {
return 'bar';
};
viewer.on('foo', 1000, handler);
// when
return viewer.importXML(xml).then(function() {
var eventBus = viewer.get('eventBus');
// when
viewer.off('foo', handler);
var result = eventBus.fire('foo');
expect(result).not.to.exist;
});
});
});
describe('#destroy', function() {
it('should remove traces in document tree', function() {
// given
var viewer = new Viewer({
container: container
});
// when
viewer.destroy();
// then
expect(viewer._container.parentNode).not.to.exist;
});
});
describe('#attachTo', function() {
it('should attach the viewer', function() {
var xml = require('../fixtures/bpmn/simple.bpmn');
var viewer = new Viewer();
return viewer.importXML(xml).then(function(result) {
// assume
expect(viewer._container.parentNode).not.to.exist;
var resizedSpy = sinon.spy();
viewer.on('canvas.resized', resizedSpy);
// when
viewer.attachTo(container);
// then
expect(viewer._container.parentNode).to.equal(container);
// should trigger resized
expect(resizedSpy).to.have.been.called;
});
});
});
describe('#detach', function() {
it('should detach the viewer', function() {
var xml = require('../fixtures/bpmn/simple.bpmn');
var viewer = new Viewer({ container: container });
return viewer.importXML(xml).then(function(result) {
// assume
expect(viewer._container.parentNode).to.equal(container);
// when
viewer.detach();
// then
expect(viewer._container.parentNode).not.to.exist;
});
});
});
describe('#clear', function() {
it('should NOT clear if no diagram', function() {
// given
var viewer = new Viewer({ container: container });
var eventBus = viewer.get('eventBus');
var spy = sinon.spy();
eventBus.on('diagram.clear', spy);
// when
viewer.clear();
// then
expect(spy).not.to.have.been.called;
});
it('should not throw if diagram is already empty', function() {
// given
var viewer = new Viewer({ container: container });
function clearDiagram() {
viewer.clear();
}
// then
expect(clearDiagram).to.not.throw();
});
});
it('default export', function() {
expect(ViewerDefaultExport).to.equal(Viewer);
});
describe('accessibility', function() {
it('should report no issues', async function() {
// given
const xml = require('../fixtures/bpmn/simple.bpmn');
await createViewer(container, Viewer, xml);
// then
await expectToBeAccessible(container);
});
});
});
================================================
FILE: test/spec/draw/BpmnRenderUtilSpec.js
================================================
import {
isTypedEvent,
isThrowEvent,
isCollection,
getDi,
getSemantic,
getCirclePath,
getRoundRectPath,
getDiamondPath,
getRectPath,
getFillColor,
getStrokeColor,
getLabelColor
} from 'lib/draw/BpmnRenderUtil';
describe('BpmnRenderUtil', function() {
it('should expose isTypedEvent', function() {
expect(isTypedEvent).to.be.a('function');
});
it('should expose isThrowEvent', function() {
expect(isThrowEvent).to.be.a('function');
});
it('should expose isCollection', function() {
expect(isCollection).to.be.a('function');
});
it('should expose getDi', function() {
expect(getDi).to.be.a('function');
});
it('should expose getSemantic', function() {
expect(getSemantic).to.be.a('function');
});
it('should expose getCirclePath', function() {
expect(getCirclePath).to.be.a('function');
});
it('should expose getRoundRectPath', function() {
expect(getRoundRectPath).to.be.a('function');
});
it('should expose getDiamondPath', function() {
expect(getDiamondPath).to.be.a('function');
});
it('should expose getRectPath', function() {
expect(getRectPath).to.be.a('function');
});
it('should expose getFillColor', function() {
expect(getFillColor).to.be.a('function');
});
it('should expose getStrokeColor', function() {
expect(getStrokeColor).to.be.a('function');
});
it('should expose getLabelColor', function() {
expect(getLabelColor).to.be.a('function');
});
});
================================================
FILE: test/spec/draw/BpmnRenderer.colors.bpmn
================================================
SequenceFlow_1jrsqqc
SequenceFlow_1jrsqqc
SequenceFlow_0h9s0mp
DataStoreReference_1clvrcw
Property_1vr5ovt
DataObjectReference_0iua582
SequenceFlow_0h9s0mp
SequenceFlow_0pqo7zt
SequenceFlow_1qt82pt
SequenceFlow_0pqo7zt
SequenceFlow_1qt82pt
SequenceFlow_17ohrlh
SequenceFlow_17ohrlh
================================================
FILE: test/spec/draw/BpmnRenderer.connection-colors.bpmn
================================================
YO
DataStoreReference_046zpex
Property_0kufae0
DataObjectReference_130kij0
================================================
FILE: test/spec/draw/BpmnRenderer.group-colors.bpmn
================================================
================================================
FILE: test/spec/draw/BpmnRenderer.labels.bpmn
================================================
================================================
FILE: test/spec/draw/BpmnRenderer.no-event-icons.bpmn
================================================
Flow_1qympxx
Flow_1635gbq
Flow_1nt6baa
Flow_1qympxx
Flow_1nt6baa
Flow_1635gbq
================================================
FILE: test/spec/draw/BpmnRenderer.sequenceFlow-no-source.bpmn
================================================
================================================
FILE: test/spec/draw/BpmnRenderer.simple-cropping.bpmn
================================================
SequenceFlow_1
SequenceFlow_1
SequenceFlow_2
SequenceFlow_2
SequenceFlow_3
SequenceFlow_3
================================================
FILE: test/spec/draw/BpmnRendererSpec.js
================================================
import {
bootstrapModeler,
bootstrapViewer,
inject
} from 'test/TestHelper';
import {
attr as svgAttr,
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,
queryAll as domQueryAll
} from 'min-dom';
import { getVisual } from 'diagram-js/lib/util/GraphicsUtil';
import { isAny } from 'lib/features/modeling/util/ModelingUtil';
import { isExpanded } from 'lib/util/DiUtil';
import { isPlane } from 'lib/util/DrilldownUtil';
import {
getDi,
black,
white
} from 'lib/draw/BpmnRenderUtil';
import customRendererModule from './custom-renderer';
import { expectSvgPath } from '../../util/svgHelpers';
/**
* @typedef {import('../../../lib/model/Types').Element} Element
*/
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 event subprocess icons', function() {
var xml = require('../../fixtures/bpmn/draw/event-subprocess-icons.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 vertical pools', function() {
var xml = require('../../fixtures/bpmn/draw/vertical-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(/^marker-[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) {
[
[ 'SequenceFlow_1jrsqqc' , 'blue' , 'blue' ],
[ 'SequenceFlow_0h9s0mp' , 'rgba(255, 0, 0, 0.9)' ],
[ 'SequenceFlow_0pqo7zt' , 'rgb(251, 140, 0)' , 'rgb(251, 140, 0)' ],
[ 'SequenceFlow_1qt82pt' , 'blue' , 'blue' ],
[ 'SequenceFlow_17ohrlh' , 'rgb(251, 140, 0)' , 'rgb(251, 140, 0)' ],
[ 'MessageFlow_11bysyp' , 'rgb(251, 140, 0)' , 'rgb(255, 224, 178)' ],
[ 'MessageFlow_1qyovto' , 'rgb(251, 140, 0)' , 'rgb(255, 224, 178)' ],
[ 'DataInputAssociation_1ncouqr' , 'rgb(251, 140, 0)' , 'none' ],
[ 'DataOutputAssociation_1i89wkc' , 'rgb(251, 140, 0)' , 'none' ]
].forEach(([ id, stroke, fill ]) => {
var svg = canvas._svg,
markerPath = svg.querySelector(`[data-element-id="${id}"] marker path`);
expect(markerPath).to.exist;
stroke && expect(markerPath.style.stroke).to.eql(stroke);
fill && expect(markerPath.style.fill).to.eql(fill);
});
})();
});
});
it('should render collapsed subprocess marker centered', 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 task = elementRegistry.getGraphics('SubProcessCollapsed');
const marker = domQuery('[data-marker=sub-process]', task);
expectDistance(task, marker, { x: 0 });
})();
});
});
it('should render compensation marker centered', 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 task = elementRegistry.getGraphics('TaskCompensation');
const marker = domQuery('[data-marker=compensation]', task);
expectDistance(task, marker, { x: 0 });
})();
});
});
it('should render ad-hoc marker centered on expanded subprocess', 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 task = elementRegistry.getGraphics('AdHocSubProcessExpanded');
const marker = domQuery('[data-marker=adhoc]', task);
expectDistance(task, marker, { x: 0 });
})();
});
});
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) {
[
[ 'MessageFlow_1facuin', 'rgb(23, 100, 255)', 'rgb(23, 100, 255)' ],
[ 'MessageFlow_1vmbq3n', 'rgb(23, 100, 255)', 'rgb(23, 100, 255)' ],
[ 'DataInputAssociation', 'rgb(23, 100, 255)', 'none' ],
[ 'DataOutputAssociation_0ixhole', 'rgb(142, 36, 170)', 'none' ],
].forEach(([ id, stroke, fill ]) => {
var svg = canvas._svg,
markerPath = svg.querySelector(`[data-element-id="${id}"] marker path`);
expect(markerPath).to.exist;
stroke && expect(markerPath.style.stroke).to.eql(stroke);
fill && expect(markerPath.style.fill).to.eql(fill);
});
})();
});
});
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
}
}));
function expectFillColor(element, color) {
expect(expectedColors(color)).to.include(element.style.fill);
}
function expectStrokeColor(element, color) {
expect(expectedColors(color)).to.include(element.style.stroke);
}
/**
* Expect colors depending on element type.
*
* @param {Element} element - Element.
* @param {SVGElement} 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('events', function() {
const diagramXML = require('../../fixtures/bpmn/draw/events.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
bpmnRenderer: {
defaultFillColor: defaultFillColor,
defaultStrokeColor: defaultStrokeColor,
defaultLabelColor: defaultLabelColor
}
}));
it('should not fill multiple parallel events', inject(function(elementRegistry) {
// given
var parallelMultiple = elementRegistry.get('StartEvent_multiple_parallel_1');
var visual = getVisual(elementRegistry.getGraphics(parallelMultiple));
var path = domQuery('path', visual);
// then
expectFillColor(path, defaultFillColor);
expectStrokeColor(path, defaultStrokeColor);
}));
});
});
});
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(elementRegistry, graphicsFactory) {
// given
var eventElement = elementRegistry.get('StartEvent_1');
// when
var startPath = graphicsFactory.getShapePath(eventElement);
// then
expect(startPath).to.equal('M187,263m0,-18a18,18,0,1,1,0,36a18,18,0,1,1,0,-36z');
}));
it('should return a diamond path', inject(function(elementRegistry, graphicsFactory) {
// given
var gatewayElement = elementRegistry.get('ExclusiveGateway_1');
// when
var gatewayPath = graphicsFactory.getShapePath(gatewayElement);
// then
expect(gatewayPath).to.equal('M358,238l25,25l-25,25l-25,-25z');
}));
it('should return a rounded rectangular path', inject(function(elementRegistry, graphicsFactory) {
// given
var subProcessElement = elementRegistry.get('SubProcess_1');
// when
var subProcessPath = graphicsFactory.getShapePath(subProcessElement);
// then
expect(subProcessPath).to.equal('M524,163l330,0a10,10,0,0,1,10,10l0,180a10,10,0,0,1,' +
'-10,10l-330,0a10,10,0,0,1,-10,-10l0,-180a10,10,0,0,1,10,-10z');
}));
it('should return a rectangular path', inject(function(elementRegistry, graphicsFactory) {
// given
var TextAnnotationElement = elementRegistry.get('TextAnnotation_1');
// when
var TextAnnotationPath = graphicsFactory.getShapePath(TextAnnotationElement);
// then
expect(TextAnnotationPath).to.equal('M308,76l100,0l0,80l-100,0z');
}));
it('should return a rounded rectangular path for external label', inject(function(elementRegistry, graphicsFactory) {
// given
const event = elementRegistry.get('StartEvent_1');
const label = event.labels[0];
// when
const labelPath = graphicsFactory.getShapePath(label);
// then
expectSvgPath(
labelPath,
'M163,303l47,0a4,4,0,0,1,4,4l0,6a4,4,0,0,1,-4,4l-47,0a4,4,0,0,1,-4,-4l0,-6a4,4,0,0,1,4,-4z'
);
}));
});
});
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;
}));
});
describe('attrs', function() {
describe('colors', function() {
const diagramXML = require('../../fixtures/bpmn/kitchen-sink.bpmn');
class CustomColors {
constructor(eventBus) {
eventBus.on([ 'render.shape', 'render.connection' ], 100000, (_, context) => {
context.attrs = {
fill: 'yellow',
fillOpacity: 0.1, // should be ignored
stroke: 'blue',
strokeDasharray: '0, 10', // should be ignored
strokeWidth: 10 // should be ignored
};
});
}
}
CustomColors.$inject = [ 'eventBus' ];
beforeEach(bootstrapModeler(diagramXML, {
bpmnRenderer: {
defaultFillColor: 'cyan',
defaultStrokeColor: 'red'
},
additionalModules: [
{
__init__: [ 'customColors' ],
customColors: [ 'type', CustomColors ]
}
]
}));
it('should override colors', inject(function(canvas) {
// then
var container = canvas.getContainer();
// expect fill and stroke overridden
domQueryAll('.djs-visual *', container).forEach(element => {
expect(svgAttr(element, 'fill')).not.to.equal('cyan');
expect(svgAttr(element, 'fill')).not.to.equal(white);
expect(svgAttr(element, 'stroke')).not.to.equal('red');
expect(svgAttr(element, 'stroke')).not.to.equal(black);
});
// expect all others not overridden
domQueryAll('.djs-visual *', container).forEach(element => {
expect(svgAttr(element, 'stroke-dasharray')).not.to.equal('0, 9000');
expect(svgAttr(element, 'stroke-width')).not.to.equal('9000');
});
}));
});
describe('bounds', function() {
const diagramXML = require('../../fixtures/bpmn/kitchen-sink.bpmn');
class CustomBounds {
constructor(eventBus) {
eventBus.on('render.shape', 100000, (_, context) => {
context.attrs = {
width: 200,
height: 100,
fillOpacity: 0.1, // should be ignored
strokeDasharray: '0, 9000', // should be ignored
strokeWidth: 9000 // should be ignored
};
});
}
}
CustomBounds.$inject = [ 'eventBus' ];
beforeEach(bootstrapModeler(diagramXML, {
additionalModules: [
{
__init__: [ 'customBounds' ],
customBounds: [ 'type', CustomBounds ]
}
]
}));
it('should override bounds', inject(function(canvas, elementRegistry) {
// then
var container = canvas.getContainer();
// expect width and height overridden
elementRegistry.filter(element => {
return isAny(element, [
'bpmn:AdHocSubProcess',
'bpmn:Group',
'bpmn:Lane',
'bpmn:Participant',
'bpmn:SubProcess',
'bpmn:TextAnnotation',
'bpmn:Transaction'
]);
}).forEach(element => {
if (isPlane(element)) {
return;
}
var visual = getVisual(elementRegistry.getGraphics(element));
var rect = domQuery('rect', visual);
if (rect) {
if (isCollapsedSubProcess(element)) {
expect(svgAttr(rect, 'width')).to.equal('100');
expect(svgAttr(rect, 'height')).to.equal('80');
} else {
expect(svgAttr(rect, 'width')).to.equal('200');
expect(svgAttr(rect, 'height')).to.equal('100');
}
}
});
// expect all others not overridden
domQueryAll('.djs-visual *', container).forEach(element => {
expect(svgAttr(element, 'stroke-dasharray')).not.to.equal('0, 9000');
expect(svgAttr(element, 'stroke-width')).not.to.equal('9000');
});
}));
});
});
describe('custom icons', function() {
var xml = require('./BpmnRenderer.no-event-icons.bpmn');
beforeEach(bootstrapViewer(xml, {
additionalModules: [ customRendererModule ]
}));
it('should render blank', inject(function(elementRegistry) {
// given
var events = [
'START_EVENT',
'THROW_EVENT',
'CATCH_EVENT',
'END_EVENT',
'BOUNDARY_EVENT'
];
for (var elementId of events) {
var gfx = elementRegistry.getGraphics(elementId);
var iconGfx = domQuery('path', gfx);
expect(iconGfx, `icon on element <#${ elementId }>`).not.to.exist;
}
}));
});
});
function isCollapsedSubProcess(element) {
return isAny(element, [
'bpmn:SubProcess',
'bpmn:AdHocSubProcess',
'bpmn:Transaction'
]) && !isExpanded(element);
}
/**
* Expect distance between two elements.
*
* @param {SVGAElement} element1
* @param {SVGAElement} element2
* @param { { x: number; y: number } } distance
* @param {number} [tolerance=3]
*
* @returns {void}
*/
function expectDistance(element1, element2, distance, tolerance = 3) {
const {
x = Infinity,
y = Infinity
} = distance;
const bbox1 = element1.getBoundingClientRect();
const bbox2 = element2.getBoundingClientRect();
const center1 = {
x: bbox1.left + (bbox1.width / 2),
y: bbox1.top + (bbox1.height / 2)
};
const center2 = {
x: bbox2.left + (bbox2.width / 2),
y: bbox2.top + (bbox2.height / 2)
};
expect(Math.abs(center1.x - center2.x)).to.be.lessThan(x + tolerance);
expect(Math.abs(center1.y - center2.y)).to.be.lessThan(y + tolerance);
}
================================================
FILE: test/spec/draw/TextRenderer.bpmn
================================================
================================================
FILE: test/spec/draw/TextRendererSpec.js
================================================
import {
bootstrapViewer,
inject
} from 'test/TestHelper';
describe('draw - TextRenderer', function() {
var diagramXML = require('./TextRenderer.bpmn');
describe('API', function() {
beforeEach(bootstrapViewer(diagramXML));
it('should expose #createText', inject(function(textRenderer) {
// when
var text = textRenderer.createText('FOO');
// then
expect(text).to.exist;
}));
it('should expose #getExternalLabelBounds', inject(function(textRenderer) {
// given
var bounds = {
x: 0,
y: 0,
width: 100,
height: 100
};
// when
var layoutedBounds = textRenderer.getExternalLabelBounds(
bounds,
'FOO\nBar\nFOOBAR'
);
// then
expect(layoutedBounds).to.exist;
expect(layoutedBounds.x).to.exist;
expect(layoutedBounds.y).to.exist;
expect(layoutedBounds.width).to.exist;
expect(layoutedBounds.height).to.exist;
}));
it('should expose #getTextAnnotationBounds', inject(function(textRenderer) {
// given
var bounds = {
x: 0,
y: 0,
width: 100,
height: 100
};
// when
var layoutedBounds = textRenderer.getTextAnnotationBounds(
bounds,
'FOO\nBar\nFOOBAR'
);
// then
expect(layoutedBounds).to.exist;
expect(layoutedBounds.x).to.exist;
expect(layoutedBounds.y).to.exist;
expect(layoutedBounds.width).to.exist;
expect(layoutedBounds.height).to.exist;
}));
});
describe('style override', function() {
beforeEach(bootstrapViewer(diagramXML, {
textRenderer: {
defaultStyle: {
fontFamily: 'monospace',
fontSize: '15px',
lineHeight: '24px'
},
externalStyle: {
fontWeight: 'bold'
}
}
}));
it('should render', inject(function(textRenderer) {
// when
var defaultStyle = textRenderer.getDefaultStyle();
var externalStyle = textRenderer.getExternalStyle();
// then
expect(defaultStyle.fontFamily).to.eql('monospace');
expect(defaultStyle.fontSize).to.eql('15px');
expect(defaultStyle.lineHeight).to.eql('24px');
expect(externalStyle.fontFamily).to.eql('monospace');
expect(externalStyle.fontSize).to.eql(14);
expect(externalStyle.fontWeight).to.eql('bold');
}));
});
});
================================================
FILE: test/spec/draw/custom-renderer/CustomRenderer.js
================================================
import inherits from 'inherits-browser';
import BaseRenderer from 'diagram-js/lib/draw/BaseRenderer';
import {
is,
isAny
} from 'lib/util/ModelUtil';
import {
isLabel
} from 'lib/util/LabelUtil';
var HIGH_PRIORITY = 1250;
export default function CustomRenderer(
bpmnRenderer,
eventBus) {
this._bpmnRenderer = bpmnRenderer;
BaseRenderer.call(this, eventBus, HIGH_PRIORITY);
}
inherits(CustomRenderer, BaseRenderer);
CustomRenderer.prototype.canRender = function(element) {
if (isLabel(element)) {
return false;
}
return !!(
isAny(element, [ 'bpmn:Event' ])
);
};
CustomRenderer.prototype.drawShape = function(parentGfx, element) {
var renderer = this._bpmnRenderer.handlers[
[
'bpmn:StartEvent',
'bpmn:IntermediateCatchEvent',
'bpmn:IntermediateThrowEvent',
'bpmn:BoundaryEvent',
'bpmn:EndEvent'
].find(t => is(element, t))
];
return renderer(parentGfx, element, { renderIcon: false });
};
CustomRenderer.$inject = [
'bpmnRenderer',
'eventBus'
];
================================================
FILE: test/spec/draw/custom-renderer/index.js
================================================
import CustomRenderer from './CustomRenderer';
export default {
__init__: [ 'customRenderer' ],
customRenderer: [ 'type', CustomRenderer ]
};
================================================
FILE: test/spec/environment/MockingSpec.js
================================================
import {
bootstrapViewer,
inject
} from 'test/TestHelper';
import Events from 'diagram-js/lib/core/EventBus';
import Viewer from 'lib/Viewer';
describe('environment - mocking', function() {
var diagramXML = require('../../fixtures/bpmn/simple.bpmn');
var mockEvents, bootstrapCalled;
beforeEach(bootstrapViewer(diagramXML, {
modules: Viewer.prototype._modules
}, function() {
mockEvents = new Events();
bootstrapCalled = true;
return {
eventBus: mockEvents
};
}));
afterEach(function() {
bootstrapCalled = false;
});
it('should use spy', inject(function(eventBus) {
expect(eventBus).to.eql(mockEvents);
expect(bootstrapCalled).to.be.true;
}));
it('should reparse bootstrap code', inject(function(eventBus) {
expect(bootstrapCalled).to.be.true;
}));
it('should inject bpmnjs', inject(function(bpmnjs) {
expect(bpmnjs).to.exist;
}));
});
================================================
FILE: test/spec/features/align-elements/AlignElementsContextPadProviderSpec.js
================================================
import {
bootstrapModeler,
getBpmnJS,
inject
} from 'test/TestHelper';
import {
query as domQuery
} from 'min-dom';
import alignElementsModule from 'lib/features/align-elements';
import modelingModule from 'lib/features/modeling';
import coreModule from 'lib/core';
describe('features/align-elements - context pad', function() {
var testModules = [ alignElementsModule, modelingModule, coreModule ];
var basicXML = require('../../../fixtures/bpmn/align-elements.bpmn');
beforeEach(bootstrapModeler(basicXML, { modules: testModules }));
it('should provide button to open menu', inject(function(elementRegistry, contextPad) {
// given
var elements = [
elementRegistry.get('EndEvent_lane'),
elementRegistry.get('Task_lane'),
elementRegistry.get('SubProcess_lane')
];
// when
contextPad.open(elements);
// then
expect(getEntry(elements, 'align-elements')).to.exist;
}));
it('should NOT provide button if no actions are available', inject(
function(elementRegistry, contextPad, popupMenu) {
// given
var elements = [
elementRegistry.get('EndEvent_lane'),
elementRegistry.get('Task_lane'),
elementRegistry.get('SubProcess_lane')
];
popupMenu.registerProvider('align-elements', 0, {
getPopupMenuEntries: function() {
return function() {
return {};
};
}
});
// when
contextPad.open(elements);
// then
expect(getEntry(elements, 'align-elements')).not.to.exist;
})
);
it('should open popup menu when item is clicked', inject(
function(elementRegistry, contextPad, popupMenu) {
// given
var elements = [
elementRegistry.get('EndEvent_lane'),
elementRegistry.get('Task_lane'),
elementRegistry.get('SubProcess_lane')
];
contextPad.open(elements);
// when
var entry = getEntry(elements, 'align-elements');
entry.click();
// then
expect(popupMenu.isOpen()).to.be.true;
})
);
});
// helper //////////////////////////////////////////////////////////////////////
function getEntry(target, actionName) {
return padEntry(getBpmnJS().invoke(function(contextPad) {
return contextPad.getPad(target).html;
}), actionName);
}
function padEntry(element, name) {
return domQuery('[data-action="' + name + '"]', element);
}
================================================
FILE: test/spec/features/align-elements/AlignElementsMenuProviderSpec.js
================================================
import {
bootstrapModeler,
getBpmnJS,
inject
} from 'test/TestHelper';
import {
query as domQuery
} from 'min-dom';
import {
forEach
} from 'min-dash';
import alignElementsModule from 'lib/features/align-elements';
import modelingModule from 'lib/features/modeling';
import coreModule from 'lib/core';
describe('features/align-elements - popup menu', function() {
var testModules = [ alignElementsModule, modelingModule, coreModule ];
var basicXML = require('../../../fixtures/bpmn/align-elements.bpmn');
beforeEach(bootstrapModeler(basicXML, { modules: testModules }));
it('should provide alignment buttons', inject(function(elementRegistry, popupMenu) {
// given
var elements = [
elementRegistry.get('EndEvent_lane'),
elementRegistry.get('Task_lane'),
elementRegistry.get('SubProcess_lane')
];
// when
popupMenu.open(elements, 'align-elements', {
x: 0,
y: 0
});
// then
forEach([
'left',
'center',
'right',
'top',
'middle',
'bottom'
], function(alignment) {
expect(getEntry('align-elements-' + alignment)).to.exist;
});
}));
it('should close popup menu when button is clicked', inject(
function(elementRegistry, popupMenu) {
// given
var elements = [
elementRegistry.get('EndEvent_lane'),
elementRegistry.get('Task_lane'),
elementRegistry.get('SubProcess_lane')
];
popupMenu.open(elements, 'align-elements', {
x: 0,
y: 0
});
var entry = getEntry('align-elements-center');
// when
entry.click();
// then
expect(popupMenu.isOpen()).to.be.false;
})
);
it('should properly size icons even with border-box', inject(function(elementRegistry, popupMenu, canvas) {
// given
var container = canvas.getContainer();
var elements = [
elementRegistry.get('EndEvent_lane'),
elementRegistry.get('Task_lane'),
elementRegistry.get('SubProcess_lane')
];
// when
container.style['box-sizing'] = 'border-box';
popupMenu.open(elements, 'align-elements', {
x: 0,
y: 0
});
// then
var entry = getEntry('align-elements-left'),
icon = domQuery('svg', entry);
var bbox = icon.getBoundingClientRect();
expect(bbox.width).to.eql(20);
expect(bbox.height).to.eql(20);
}));
});
// helper //////////////////////////////////////////////////////////////////////
function getEntry(actionName) {
return padEntry(getBpmnJS().invoke(function(popupMenu) {
return popupMenu._current.container;
}), actionName);
}
function padEntry(element, name) {
return domQuery('[data-id="' + name + '"]', element);
}
================================================
FILE: test/spec/features/align-elements/BpmnAlignElementsSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import alignElementsModule from 'lib/features/align-elements';
import modelingModule from 'lib/features/modeling';
import coreModule from 'lib/core';
describe('features/align-elements', function() {
var testModules = [ alignElementsModule, modelingModule, coreModule ];
var basicXML = require('../../../fixtures/bpmn/align-elements.bpmn');
beforeEach(bootstrapModeler(basicXML, { modules: testModules }));
describe('integration', function() {
it('should align to the left', inject(function(elementRegistry, alignElements) {
// given
var taskBoundEvt = elementRegistry.get('Task_boundary_evt'),
task = elementRegistry.get('Task_lane'),
subProcess = elementRegistry.get('SubProcess_lane'),
endEvent = elementRegistry.get('EndEvent_lane'),
elements = [ taskBoundEvt, task, subProcess, endEvent ];
// when
alignElements.trigger(elements, 'left');
// then
expect(taskBoundEvt.x).to.equal(276);
expect(task.x).to.equal(276);
expect(subProcess.x).to.equal(276);
expect(endEvent.x).to.equal(276);
}));
it('should align to the right', inject(function(elementRegistry, alignElements) {
// given
var taskHello = elementRegistry.get('Task_hello'),
task = elementRegistry.get('Task_lane'),
subProcess = elementRegistry.get('SubProcess_lane'),
endEvent = elementRegistry.get('EndEvent_lane'),
elements = [ taskHello, task, subProcess, endEvent ];
// when
alignElements.trigger(elements, 'right');
// then
expect(task.x).to.equal(860);
expect(taskHello.x).to.equal(860);
expect(subProcess.x).to.equal(610);
expect(endEvent.x).to.equal(924);
}));
it('should align to the center', inject(function(elementRegistry, alignElements) {
// given
var task = elementRegistry.get('Task_lane'),
taskHello = elementRegistry.get('Task_hello'),
subProcess = elementRegistry.get('SubProcess_lane'),
endEvent = elementRegistry.get('EndEvent_lane'),
elements = [ task, taskHello, subProcess, endEvent ];
// when
alignElements.trigger(elements, 'center');
// then
expect(task.x).to.equal(568);
expect(taskHello.x).to.equal(568);
expect(subProcess.x).to.equal(443);
expect(endEvent.x).to.equal(600);
}));
it('should align to the top', inject(function(elementRegistry, alignElements) {
// given
var task = elementRegistry.get('Task_lane'),
subProcess = elementRegistry.get('SubProcess_lane'),
endEvent = elementRegistry.get('EndEvent_lane'),
elements = [ task, subProcess, endEvent ];
// when
alignElements.trigger(elements, 'top');
// then
expect(task.y).to.equal(445);
expect(subProcess.y).to.equal(445);
expect(endEvent.y).to.equal(445);
}));
it('should align to the bottom', inject(function(elementRegistry, alignElements) {
// given
var task = elementRegistry.get('Task_lane'),
subProcess = elementRegistry.get('SubProcess_lane'),
endEvent = elementRegistry.get('EndEvent_lane'),
elements = [ task, subProcess, endEvent ];
// when
alignElements.trigger(elements, 'bottom');
// then
expect(task.y).to.equal(831);
expect(subProcess.y).to.equal(711);
expect(endEvent.y).to.equal(875);
}));
it('should align to the middle', inject(function(elementRegistry, alignElements) {
// given
var task = elementRegistry.get('Task_lane'),
subProcess = elementRegistry.get('SubProcess_lane'),
endEvent = elementRegistry.get('EndEvent_lane'),
elements = [ task, subProcess, endEvent ];
// when
alignElements.trigger(elements, 'middle');
// then
expect(task.y).to.equal(638);
expect(subProcess.y).to.equal(578);
expect(endEvent.y).to.equal(660);
}));
});
describe('rules', function() {
it('should not align boundary event', inject(function(alignElements, elementRegistry) {
// given
var boundaryEvent = elementRegistry.get('BoundaryEvent_1'),
host = elementRegistry.get('Task_boundary_evt');
var elements = [
host,
elementRegistry.get('Task_hello'),
boundaryEvent
];
var initialRelativePosition = {
x: boundaryEvent.x - host.x,
y: boundaryEvent.y - host.y
};
// when
alignElements.trigger(elements, 'middle');
// then
expect(boundaryEvent.x).to.equal(initialRelativePosition.x + host.x);
expect(boundaryEvent.y).to.equal(initialRelativePosition.y + host.y);
}));
it('should not align container children', inject(
function(alignElements, elementRegistry) {
// given
var elements = elementRegistry.getAll('SubProcessChild').slice(1),
child = elementRegistry.get('Task_hello');
var initialRelativePosition = {
x: child.x - child.parent.x,
y: child.y - child.parent.y
};
// when
alignElements.trigger(elements, 'middle');
// then
expect(child.x).to.equal(initialRelativePosition.x + child.parent.x);
expect(child.y).to.equal(initialRelativePosition.y + child.parent.y);
})
);
});
});
================================================
FILE: test/spec/features/append-preview/AppendPreview.bpmn
================================================
================================================
FILE: test/spec/features/append-preview/AppendPreviewSpec.js
================================================
import { queryAll as domQueryAll } from 'min-dom';
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import appendPreviewModule from 'lib/features/append-preview';
import coreModule from 'lib/core';
describe('features/append-preview', function() {
var diagramXML = require('./AppendPreview.bpmn');
before(bootstrapModeler(diagramXML, {
modules: [
coreModule,
appendPreviewModule
]
}));
it('should create', inject(function(appendPreview, canvas, elementRegistry) {
// given
var startEvent = elementRegistry.get('StartEvent_1');
// when
appendPreview.create(startEvent, 'bpmn:Task');
// then
expect(canvas.getLayer('complex-preview')).to.exist;
expect(domQueryAll('.djs-dragger', canvas.getLayer('complex-preview'))).to.have.length(2);
}));
it('should clean up', inject(function(appendPreview, canvas, elementRegistry) {
// given
var startEvent = elementRegistry.get('StartEvent_1');
// when
appendPreview.create(startEvent, 'bpmn:Task');
// assume
expect(canvas.getLayer('complex-preview')).to.exist;
expect(domQueryAll('.djs-dragger', canvas.getLayer('complex-preview'))).to.have.length(2);
// when
appendPreview.cleanUp();
// then
expect(domQueryAll('.djs-dragger', canvas.getLayer('complex-preview'))).to.be.empty;
}));
});
================================================
FILE: test/spec/features/auto-place/BpmnAutoPlace.boundary-events.bpmn
================================================
SequenceFlow_0o6gp3o
SequenceFlow_0o6gp3o
================================================
FILE: test/spec/features/auto-place/BpmnAutoPlace.bpmn
================================================
SequenceFlow_16tlpj7
SequenceFlow_16tlpj7
SequenceFlow_19p2kv6
SequenceFlow_18dnq8n
DataStoreReference_0r0lie7
SequenceFlow_18dnq8n
SequenceFlow_0n4l6q7
SequenceFlow_10nwqsy
SequenceFlow_13ubee5
SequenceFlow_0n4l6q7
SequenceFlow_10nwqsy
SequenceFlow_13ubee5
SequenceFlow_19p2kv6
SequenceFlow_0s1mty3
SequenceFlow_1
SequenceFlow_0s1mty3
SequenceFlow_1
================================================
FILE: test/spec/features/auto-place/BpmnAutoPlace.multi-connection.bpmn
================================================
SequenceFlow_0uj471x
SequenceFlow_0sys8ww
SequenceFlow_1q8yl3p
SequenceFlow_1dh9p3h
SequenceFlow_0uj471x
SequenceFlow_1dh9p3h
SequenceFlow_0sys8ww
SequenceFlow_1q8yl3p
================================================
FILE: test/spec/features/auto-place/BpmnAutoPlace.subprocess.bpmn
================================================
Flow_1qlbfsz
Flow_1qlbfsz
Flow_0au85uv
Flow_0au85uv
================================================
FILE: test/spec/features/auto-place/BpmnAutoPlace.subprocess.horizontal.bpmn
================================================
Flow_1jtt4p9
Flow_06nuoit
Flow_1jtt4p9
Flow_06nuoit
================================================
FILE: test/spec/features/auto-place/BpmnAutoPlace.subprocess.vertical.bpmn
================================================
Flow_1ijzj0g
Flow_1ijzj0g
Flow_01mvjj3
Flow_01mvjj3
================================================
FILE: test/spec/features/auto-place/BpmnAutoPlace.vertical.boundary-events.bpmn
================================================
Flow_1y0zvux
Flow_1y0zvux
================================================
FILE: test/spec/features/auto-place/BpmnAutoPlace.vertical.bpmn
================================================
Flow_1mfxkzi
Flow_1mfxkzi
Flow_0hd2tlx
Flow_0hd2tlx
Flow_05pkcjk
Flow_05pkcjk
SequenceFlow_1
Flow_1atamhe
DataStoreReference_0urqui4
Flow_1atamhe
Flow_07bte1m
Flow_1u4h0mv
Flow_1fj63ew
Flow_1fj63ew
SequenceFlow_1
Flow_1u4h0mv
Flow_07bte1m
================================================
FILE: test/spec/features/auto-place/BpmnAutoPlace.vertical.multi-connection.bpmn
================================================
SequenceFlow_0uj471x
SequenceFlow_0sys8ww
SequenceFlow_1q8yl3p
SequenceFlow_1dh9p3h
SequenceFlow_0sys8ww
SequenceFlow_1q8yl3p
SequenceFlow_0uj471x
SequenceFlow_1dh9p3h
================================================
FILE: test/spec/features/auto-place/BpmnAutoPlaceSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import autoPlaceModule from 'lib/features/auto-place';
import coreModule from 'lib/core';
import labelEditingModule from 'lib/features/label-editing';
import modelingModule from 'lib/features/modeling';
import selectionModule from 'diagram-js/lib/features/selection';
import { getBusinessObject } from '../../../../lib/util/ModelUtil';
describe('features/auto-place', function() {
describe('element placement', function() {
var diagramXML = require('./BpmnAutoPlace.bpmn');
before(bootstrapModeler(diagramXML, {
modules: [
coreModule,
modelingModule,
autoPlaceModule,
selectionModule
]
}));
describe('should place bpmn:FlowNode', function() {
it('at default distance after START_EVENT_1', autoPlace({
element: 'bpmn:Task',
behind: 'START_EVENT_1',
expectedBounds: { x: 1252, y: 324, width: 100, height: 80 }
}));
it('at incoming distance after TASK_0', autoPlace({
element: 'bpmn:Task',
behind: 'TASK_0',
expectedBounds: { x: 462, y: 154, width: 100, height: 80 }
}));
it('at incoming distance / quorum after TASK_5', autoPlace({
element: 'bpmn:Task',
behind: 'TASK_5',
expectedBounds: { x: 496, y: 490, width: 100, height: 80 }
}));
it('at existing outgoing / below TASK_2', autoPlace({
element: 'bpmn:Task',
behind: 'TASK_1',
expectedBounds: { x: 479, y: 400, width: 100, height: 80 }
}));
it('ignoring existing, far away outgoing of TASK_3', autoPlace({
element: 'bpmn:Task',
behind: 'TASK_3',
expectedBounds: { x: 946, y: 227, width: 100, height: 80 }
}));
it('behind bpmn:SubProcess', autoPlace({
element: 'bpmn:Task',
behind: 'SUBPROCESS_1',
expectedBounds: { x: 1125, y: 468, width: 100, height: 80 }
}));
it('after TASK_6', autoPlace({
element: 'bpmn:Task',
behind: 'TASK_6',
expectedBounds: { x: 900, y: 470, width: 100, height: 80 }
}));
});
describe('should place bpmn:DataStoreReference', function() {
it('bottom right of source', autoPlace({
element: 'bpmn:DataStoreReference',
behind: 'TASK_2',
expectedBounds: { x: 569, y: 410, width: 50, height: 50 }
}));
it('next to existing', autoPlace({
element: 'bpmn:DataStoreReference',
behind: 'TASK_3',
expectedBounds: { x: 969, y: 347, width: 50, height: 50 }
}));
});
describe('should place bpmn:TextAnnotation', function() {
it('top right of source', autoPlace({
element: 'bpmn:TextAnnotation',
behind: 'TASK_2',
expectedBounds: { x: 579, y: 210, width: 100, height: 30 }
}));
it('above existing', autoPlace({
element: 'bpmn:TextAnnotation',
behind: 'TASK_3',
expectedBounds: { x: 896, y: 96, width: 100, height: 30 }
}));
describe('on connection', function() {
it('top right', autoPlace({
element: 'bpmn:TextAnnotation',
behind: 'SequenceFlow_1',
expectedBounds: { x: 500, y: 265, width: 100, height: 30 }
}));
it('above existing', autoPlace({
element: 'bpmn:TextAnnotation',
behind: 'SequenceFlow_1',
expectedBounds: { x: 500, y: 205, width: 100, height: 30 }
}));
});
describe('on messageflow', function() {
it('bottom right', autoPlace({
element: 'bpmn:TextAnnotation',
behind: 'MessageFlow_1',
expectedBounds: { x: 579, y: 580, width: 100, height: 30 }
}));
it('next to existing', autoPlace({
element: 'bpmn:TextAnnotation',
behind: 'MessageFlow_1',
expectedBounds: { x: 709, y: 580, width: 100, height: 30 }
}));
});
});
});
describe('vertical element placement', function() {
var diagramXML = require('./BpmnAutoPlace.vertical.bpmn');
before(bootstrapModeler(diagramXML, {
modules: [
coreModule,
modelingModule,
autoPlaceModule,
selectionModule
]
}));
describe('should place bpmn:FlowNode', function() {
it('at default distance after Start Event', autoPlace({
element: 'bpmn:Task',
behind: 'Start_Event',
expectedBounds: { x: 570, y: 1088, width: 100, height: 80 }
}));
it('at incoming distance after V_Task_0', autoPlace({
element: 'bpmn:Task',
behind: 'V_Task_0',
expectedBounds: { x: 260, y: 422, width: 100, height: 80 }
}));
it('at incoming distance / quorum after V_Task_5', autoPlace({
element: 'bpmn:Task',
behind: 'V_Task_5',
expectedBounds: { x: 590, y: 452, width: 100, height: 80 }
}));
it('at existing outgoing / right of V_Task_2', autoPlace({
element: 'bpmn:Task',
behind: 'V_Task_1',
expectedBounds: { x: 530, y: 450, width: 100, height: 80 }
}));
it('ignoring existing, far away outgoing of V_Task_3', autoPlace({
element: 'bpmn:Task',
behind: 'V_Task_3',
expectedBounds: { x: 450, y: 1090, width: 100, height: 80 }
}));
it('behind bpmn:SubProcess', autoPlace({
element: 'bpmn:Task',
behind: 'V_Sub_Process_1',
expectedBounds: { x: 699, y: 930, width: 100, height: 80 }
}));
it('below V_TASK_6', autoPlace({
element: 'bpmn:Task',
behind: 'V_TASK_6',
expectedBounds: { x: 700, y: 730, width: 100, height: 80 }
}));
});
describe('should place bpmn:DataStoreReference', function() {
it('bottom right of source', autoPlace({
element: 'bpmn:DataStoreReference',
behind: 'V_Task_2',
expectedBounds: { x: 310, y: 520, width: 50, height: 50 }
}));
it('next to existing', autoPlace({
element: 'bpmn:DataStoreReference',
behind: 'V_Task_3',
expectedBounds: { x: 230, y: 915, width: 50, height: 50 }
}));
});
describe('should place bpmn:TextAnnotation', function() {
it('bottom right of source', autoPlace({
element: 'bpmn:TextAnnotation',
behind: 'V_Task_2',
expectedBounds: { x: 550, y: 530, width: 100, height: 30 }
}));
it('right of existing', autoPlace({
element: 'bpmn:TextAnnotation',
behind: 'V_Task_3',
expectedBounds: { x: 600, y: 840, width: 100, height: 30 }
}));
describe('on connection', function() {
it('bottom right', autoPlace({
element: 'bpmn:TextAnnotation',
behind: 'SequenceFlow_1',
expectedBounds: { x: 500, y: 445, width: 100, height: 30 }
}));
it('right of existing', autoPlace({
element: 'bpmn:TextAnnotation',
behind: 'SequenceFlow_1',
expectedBounds: { x: 630, y: 445, width: 100, height: 30 }
}));
});
});
});
describe('integration', function() {
var diagramXML = require('./BpmnAutoPlace.bpmn');
before(bootstrapModeler(diagramXML, {
modules: [
autoPlaceModule,
coreModule,
labelEditingModule,
modelingModule,
selectionModule
]
}));
it('should complete direct edit on autoPlace', inject(
function(autoPlace, directEditing, elementFactory, elementRegistry) {
// given
var element = elementFactory.createShape({ type: 'bpmn:Task' });
var source = elementRegistry.get('TASK_2');
directEditing.activate(source);
directEditing._textbox.content.textContent = 'foo';
// when
autoPlace.append(source, element);
// then
expect(getBusinessObject(source).name).to.equal('foo');
}
));
it('should select + direct edit on autoPlace', inject(
function(autoPlace, elementRegistry, elementFactory, selection, directEditing) {
// given
var element = elementFactory.createShape({ type: 'bpmn:Task' });
var source = elementRegistry.get('TASK_2');
// when
var newShape = autoPlace.append(source, element);
// then
expect(selection.get()).to.eql([ newShape ]);
expect(directEditing.isActive()).to.be.true;
expect(directEditing._active.element).to.equal(newShape);
}
));
});
describe('multi connection handling', function() {
var diagramXML = require('./BpmnAutoPlace.multi-connection.bpmn');
before(bootstrapModeler(diagramXML, {
modules: [
coreModule,
modelingModule,
autoPlaceModule,
selectionModule,
labelEditingModule
]
}));
it('should ignore multiple source -> target connections', inject(
function(autoPlace, elementRegistry, elementFactory, selection, directEditing) {
// given
var element = elementFactory.createShape({ type: 'bpmn:Task' });
var source = elementRegistry.get('TASK_1');
var alignedElement = elementRegistry.get('TASK_3');
// when
var newShape = autoPlace.append(source, element);
// then
expect(newShape.x).to.eql(alignedElement.x);
}
));
});
describe('vertical multi connection handling', function() {
var diagramXML = require('./BpmnAutoPlace.vertical.multi-connection.bpmn');
before(bootstrapModeler(diagramXML, {
modules: [
coreModule,
modelingModule,
autoPlaceModule,
selectionModule,
labelEditingModule
]
}));
it('should ignore multiple source -> target connections', inject(
function(autoPlace, elementRegistry, elementFactory, selection, directEditing) {
// given
var element = elementFactory.createShape({ type: 'bpmn:Task' });
var source = elementRegistry.get('V_TASK_1');
var alignedElement = elementRegistry.get('V_TASK_3');
// when
var newShape = autoPlace.append(source, element);
// then
expect(newShape.y).to.eql(alignedElement.y);
}
));
});
describe('boundary event connection handling', function() {
var diagramXML = require('./BpmnAutoPlace.boundary-events.bpmn');
before(bootstrapModeler(diagramXML, {
modules: [
coreModule,
modelingModule,
autoPlaceModule,
selectionModule
]
}));
it('should place bottom right of BOUNDARY_BOTTOM', autoPlace({
element: 'bpmn:Task',
behind: 'BOUNDARY_BOTTOM',
expectedBounds: { x: 241, y: 213, width: 100, height: 80 }
}));
it('should place bottom right of BOUNDARY_SUBPROCESS_BOTTOM', autoPlace({
element: 'bpmn:Task',
behind: 'BOUNDARY_SUBPROCESS_BOTTOM',
expectedBounds: { x: 278, y: 495, width: 100, height: 80 }
}));
it('should place top right of BOUNDARY_TOP', autoPlace({
element: 'bpmn:Task',
behind: 'BOUNDARY_TOP',
expectedBounds: { x: 242, y: -27, width: 100, height: 80 }
}));
it('should place top right of BOUNDARY_TOP_RIGHT without infinite loop', autoPlace({
element: 'bpmn:Task',
behind: 'BOUNDARY_TOP_RIGHT',
expectedBounds: { x: 473, y: -27, width: 100, height: 80 }
}));
it('should place top right of BOUNDARY_SUBPROCESS_TOP', autoPlace({
element: 'bpmn:Task',
behind: 'BOUNDARY_SUBPROCESS_TOP',
expectedBounds: { x: 275, y: 194, width: 100, height: 80 }
}));
});
describe('vertical boundary event connection handling', function() {
var diagramXML = require('./BpmnAutoPlace.vertical.boundary-events.bpmn');
before(bootstrapModeler(diagramXML, {
modules: [
coreModule,
modelingModule,
autoPlaceModule,
selectionModule
]
}));
it('should place bottom right of V_BOUNDARY_RIGHT', autoPlace({
element: 'bpmn:Task',
behind: 'V_BOUNDARY_RIGHT',
expectedBounds: { x: 420, y: 268, width: 100, height: 80 }
}));
it('should place bottom right of V_BOUNDARY_SUBPROCESS_RIGHT', autoPlace({
element: 'bpmn:Task',
behind: 'V_BOUNDARY_SUBPROCESS_RIGHT',
expectedBounds: { x: 740, y: 278, width: 100, height: 80 }
}));
it('should place bottom left of V_BOUNDARY_LEFT', autoPlace({
element: 'bpmn:Task',
behind: 'V_BOUNDARY_LEFT',
expectedBounds: { x: 140, y: 268, width: 100, height: 80 }
}));
it('should place bottom left of V_BOUNDARY_BOTTOM_LEFT', autoPlace({
element: 'bpmn:Task',
behind: 'V_BOUNDARY_BOTTOM_LEFT',
expectedBounds: { x: 140, y: 438, width: 100, height: 80 }
}));
it('should place bottom left of V_BOUNDARY_SUBPROCESS_LEFT', autoPlace({
element: 'bpmn:Task',
behind: 'V_BOUNDARY_SUBPROCESS_LEFT',
expectedBounds: { x: 420, y: 278, width: 100, height: 80 }
}));
});
describe('nested element placement', function() {
describe('in collapsed subprocess', function() {
var diagramXML = require('./BpmnAutoPlace.subprocess.bpmn');
before(bootstrapModeler(diagramXML, {
modules: [
coreModule,
modelingModule,
autoPlaceModule
]
}));
beforeEach(inject(function(canvas) {
canvas.setRootElement(canvas.findRoot('Sub_Process_plane'));
}));
it('should place node horizontally after Nested_Start_Event', autoPlace({
element: 'bpmn:Task',
behind: 'Nested_Start_Event',
expectedBounds: { x: 265, y: 57, width: 100, height: 80 }
}));
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 }
}));
it('should place data store horizontally below Nested_Start_Event', autoPlace({
element: 'bpmn:DataStoreReference',
behind: 'Nested_Start_Event',
expectedBounds: { x: 205, y: 155, width: 50, height: 50 }
}));
});
describe('in collapsed horizontal subprocess', function() {
var diagramXML = require('./BpmnAutoPlace.subprocess.horizontal.bpmn');
before(bootstrapModeler(diagramXML, {
modules: [
coreModule,
modelingModule,
autoPlaceModule
]
}));
beforeEach(inject(function(canvas) {
canvas.setRootElement(canvas.findRoot('Sub_Process_plane'));
}));
it('should place node horizontally after Nested_Start_Event', autoPlace({
element: 'bpmn:Task',
behind: 'Nested_Start_Event',
expectedBounds: { x: 265, y: 77, width: 100, height: 80 }
}));
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 }
}));
it('should place data store horizontally below Nested_Start_Event', autoPlace({
element: 'bpmn:DataStoreReference',
behind: 'Nested_Start_Event',
expectedBounds: { x: 205, y: 175, width: 50, height: 50 }
}));
});
describe('in collapsed vertical subprocess', function() {
var diagramXML = require('./BpmnAutoPlace.subprocess.vertical.bpmn');
before(bootstrapModeler(diagramXML, {
modules: [
coreModule,
modelingModule,
autoPlaceModule
]
}));
beforeEach(inject(function(canvas) {
canvas.setRootElement(canvas.findRoot('Sub_Process_plane'));
}));
it('should place node vertically after Nested_Start_Event', autoPlace({
element: 'bpmn:Task',
behind: 'Nested_Start_Event',
expectedBounds: { x: 127, y: 165, width: 100, height: 80 }
}));
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 }
}));
it('should place data store vertically left of Nested_Start_Event', autoPlace({
element: 'bpmn:DataStoreReference',
behind: 'Nested_Start_Event',
expectedBounds: { x: 69, y: 105, width: 50, height: 50 }
}));
});
});
});
// helpers //////////
function autoPlace(cfg) {
var element = cfg.element,
behind = cfg.behind,
expectedBounds = cfg.expectedBounds;
return inject(function(autoPlace, elementRegistry, elementFactory) {
var sourceEl = elementRegistry.get(behind);
// assume
expect(sourceEl).to.exist;
if (typeof element === 'string') {
element = { type: element };
}
var shape = elementFactory.createShape(element);
// when
var placedShape = autoPlace.append(sourceEl, shape);
// then
expect(placedShape).to.have.bounds(expectedBounds);
});
}
================================================
FILE: test/spec/features/auto-resize/AutoResize.lanes.bpmn
================================================
StartEvent_1
Task_1
SequenceFlow_1
SequenceFlow_1
================================================
FILE: test/spec/features/auto-resize/AutoResize.multi-selection.bpmn
================================================
================================================
FILE: test/spec/features/auto-resize/AutoResize.nested-sub-processes.bpmn
================================================
SequenceFlow_1
SequenceFlow_1
================================================
FILE: test/spec/features/auto-resize/AutoResize.participant.bpmn
================================================
SequenceFlow_1
SequenceFlow_1
================================================
FILE: test/spec/features/auto-resize/AutoResize.space-tool.bpmn
================================================
SequenceFlow_1
SequenceFlow_1
================================================
FILE: test/spec/features/auto-resize/AutoResize.sub-processes.bpmn
================================================
SequenceFlow_1
SequenceFlow_1
================================================
FILE: test/spec/features/auto-resize/AutoResizeSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import {
pick,
assign
} from 'min-dash';
import autoResizeModule from 'lib/features/auto-resize';
import modelingModule from 'lib/features/modeling';
import createModule from 'diagram-js/lib/features/create';
import coreModule from 'lib/core';
import moveModule from 'diagram-js/lib/features/move';
import {
createCanvasEvent as canvasEvent
} from '../../../util/MockEvents';
function getBounds(shape) {
return pick(shape, [ 'x', 'y', 'width', 'height' ]);
}
describe('features/auto-resize', function() {
var testModules = [
coreModule,
modelingModule,
autoResizeModule,
createModule,
moveModule
];
describe('participant', function() {
var diagramXML = require('./AutoResize.participant.bpmn');
var taskShape,
participantShape,
startEventShape,
originalBounds;
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
beforeEach(inject(function(elementRegistry) {
taskShape = elementRegistry.get('Task_1');
participantShape = elementRegistry.get('Participant_1');
startEventShape = elementRegistry.get('StartEvent_1');
originalBounds = getBounds(participantShape);
expect(originalBounds).to.eql({ x: 247, y: 160, width: 371, height: 178 });
}));
describe('after moving', function() {
it('should expand the right edge', inject(function(modeling) {
// when
modeling.moveElements([ taskShape ], { x: 100, y: 0 }, participantShape);
// then
var expectedBounds = assign(originalBounds, { width: 525 });
expect(participantShape).to.have.bounds(expectedBounds);
}));
it('should expand the top edge', inject(function(modeling) {
// when
modeling.moveElements([ taskShape ], { x: 0, y: -50 }, participantShape);
// then
var expectedBounds = assign(originalBounds, { y: 99, height: 239 });
expect(participantShape).to.have.bounds(expectedBounds);
}));
it('should expand the bottom edge', inject(function(modeling) {
// when
modeling.moveElements([ taskShape ], { x: 0, y: 50 }, participantShape);
// then
var expectedBounds = assign(originalBounds, { height: 239 });
expect(participantShape).to.have.bounds(expectedBounds);
}));
it('should expand the left edge', inject(function(modeling) {
// when
modeling.moveElements([ startEventShape ], { x: -100, y: 0 }, participantShape);
// then
var expectedBounds = assign(originalBounds, { x: 122, width: 496 });
expect(participantShape).to.have.bounds(expectedBounds);
}));
it('should expand the bottom right edges', inject(function(modeling) {
// when
modeling.moveElements([ taskShape ], { x: 50, y: 50 }, participantShape);
// then
var expectedBounds = assign(originalBounds, { width: 475, height: 239 });
expect(participantShape).to.have.bounds(expectedBounds);
}));
it('should expand the top left edges', inject(function(modeling) {
// when
modeling.moveElements([ startEventShape ], { x: -100, y: -100 }, participantShape);
// then
expect(participantShape).to.have.bounds({ x: 122, y: 71, width: 496, height: 267 });
}));
it('should resize the parent on element/parent edge intersect', inject(function(modeling) {
// when
modeling.moveElements([ taskShape ], { x: 0, y: 49 }, participantShape);
// then
var expectedBounds = assign(originalBounds, { height: 238 });
expect(participantShape).to.have.bounds(expectedBounds);
}));
it('should not resize the parent if element is placed near the bottom', inject(function(modeling) {
// when
modeling.moveElements([ taskShape ], { x: 0, y: 47 }, participantShape);
// then
expect(participantShape).to.have.bounds(originalBounds);
}));
describe('undo / redo support', function() {
it('should undo', inject(function(modeling, commandStack) {
// when
modeling.moveElements([ startEventShape ], { x: -100, y: -100 }, participantShape);
commandStack.undo();
// then
expect(participantShape).to.have.bounds(originalBounds);
}));
it('should redo', inject(function(modeling, commandStack) {
// when
modeling.moveElements([ startEventShape ], { x: -100, y: -100 }, participantShape);
commandStack.undo();
commandStack.redo();
// then
expect(participantShape).to.have.bounds({ x: 122, y: 71, width: 496, height: 267 });
}));
});
});
describe('after moving multiple elements', function() {
it('should expand the right edge', inject(function(modeling, selection) {
// when
modeling.moveElements([ taskShape, startEventShape ], { x: 200, y: 0 }, participantShape);
// then
var expectedBounds = assign(originalBounds, { width: 625 });
expect(participantShape).to.have.bounds(expectedBounds);
}));
it('should expand the bottom edge', inject(function(modeling, selection) {
// when
modeling.moveElements([ taskShape, startEventShape ], { x: 0, y: 48 }, participantShape);
// then
var expectedBounds = assign(originalBounds, { height: 237 });
expect(participantShape).to.have.bounds(expectedBounds);
}));
});
describe('after appending', function() {
it('should expand the bottom right edges', inject(function(modeling) {
// when
modeling.appendShape(taskShape, { type: 'bpmn:Task' }, { x: 660, y: 350 }, participantShape);
// then
var expectedBounds = assign(originalBounds, { width: 563, height: 290 });
expect(participantShape).to.have.bounds(expectedBounds);
}));
it('should undo resizing', inject(function(modeling, commandStack) {
// given
modeling.appendShape(taskShape, { type: 'bpmn:Task' }, { x: 660, y: 250 }, participantShape);
// when
commandStack.undo();
// then
expect(participantShape).to.have.bounds(originalBounds);
}));
it('should redo resizing and restore shapes and connections',
inject(function(modeling, commandStack) {
// given
var taskShape2 = modeling.appendShape(taskShape, { type: 'bpmn:Task' }, { x: 660, y: 250 }, participantShape);
// when
commandStack.undo();
commandStack.redo();
// then
var expectedBounds = assign(originalBounds, { width: 563 });
expect(participantShape).to.have.bounds(expectedBounds);
expect(taskShape2).to.exist;
expect(taskShape.outgoing).not.to.be.empty;
expect(taskShape2.incoming).not.to.be.empty;
})
);
});
it('should not auto-resize when adding lane', inject(function(modeling) {
// given
var laneAttrs = {
type: 'bpmn:Lane',
width: 341,
height: 178
};
// when
modeling.createShape(laneAttrs, { x: 280, y: 200 }, participantShape);
// then
expect(participantShape).to.have.bounds(originalBounds);
}));
});
describe('lane', function() {
var diagramXML = require('./AutoResize.lanes.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
it('should fit new element', inject(function(elementRegistry, modeling) {
// given
var participantShape = elementRegistry.get('Participant_Lanes');
// when
modeling.createShape({ type: 'bpmn:Task' }, { x: 600, y: 320 }, participantShape);
// then
expect(participantShape).to.have.bounds({ x: 247, y: 160, width: 503, height: 260 });
}));
it('should fit multiple moved elements', inject(function(elementRegistry, modeling) {
// given
var participantShape = elementRegistry.get('Participant_Lanes'),
taskShape = elementRegistry.get('Task_1'),
startEventShape = elementRegistry.get('StartEvent_1');
var originalBounds = getBounds(participantShape);
// when
modeling.moveElements([ taskShape, startEventShape ], { x: 200, y: 0 }, participantShape);
// then
var expectedBounds = assign(originalBounds, { width: 625 });
expect(participantShape).to.have.bounds(expectedBounds);
}));
});
describe('sub processes', function() {
var diagramXML = require('./AutoResize.sub-processes.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
it('should auto-resize after moving children', inject(function(elementRegistry, modeling) {
// given
var subProcessShape = elementRegistry.get('SubProcess_1'),
taskShape = elementRegistry.get('Task_1'),
startEventShape = elementRegistry.get('StartEvent_1');
var originalBounds = getBounds(subProcessShape);
// when
modeling.moveElements([ taskShape, startEventShape ], { x: 200, y: 0 }, subProcessShape);
// then
var expectedBounds = assign(originalBounds, { width: 567 });
expect(subProcessShape).to.have.bounds(expectedBounds);
}));
it('should auto-resize to fit new element', inject(function(elementRegistry, modeling) {
// given
var subProcessShape = elementRegistry.get('SubProcess_1');
var originalBounds = getBounds(subProcessShape);
// when
modeling.createShape({ type: 'bpmn:Task' }, { x: 450, y: 250 }, subProcessShape);
// then
var expectedBounds = assign(originalBounds, { width: 480, height: 298 });
expect(subProcessShape).to.have.bounds(expectedBounds);
}));
it('should auto-resize after dropping selection inside',
inject(function(selection, move, dragging, elementRegistry, modeling) {
// given
var subProcessShape = elementRegistry.get('SubProcess_1'),
taskShape = elementRegistry.get('Task_1'),
startEventShape = elementRegistry.get('StartEvent_1');
var originalBounds = getBounds(subProcessShape);
// when
selection.select([ taskShape, startEventShape ]);
move.start(canvasEvent({ x: 265, y: 235 }), startEventShape);
dragging.hover({
element: subProcessShape,
gfx: elementRegistry.getGraphics(subProcessShape)
});
dragging.move(canvasEvent({ x: 450, y: 235 }));
dragging.end();
// then
var expectedBounds = assign(originalBounds, { width: 552 });
expect(subProcessShape).to.have.bounds(expectedBounds);
})
);
it('should not auto-resize after dropping selection outside',
inject(function(selection, canvas, move, dragging, elementRegistry, modeling) {
// given
var subProcessShape = elementRegistry.get('SubProcess_1'),
taskShape = elementRegistry.get('Task_1'),
startEventShape = elementRegistry.get('StartEvent_1'),
rootShape = canvas.getRootElement();
var originalBounds = getBounds(subProcessShape);
// when
selection.select([ taskShape, startEventShape ]);
move.start(canvasEvent({ x: 390, y: 110 }), taskShape);
dragging.hover({
element: rootShape,
gfx: elementRegistry.getGraphics(rootShape)
});
dragging.move(canvasEvent({ x: 600, y: 110 }));
dragging.end();
// then
expect(subProcessShape).to.have.bounds(originalBounds);
})
);
});
describe('after moving multiple elements', function() {
var diagramXML = require('./AutoResize.multi-selection.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
var taskShape_1,
taskShape_2,
subProcessShape_1,
rootShape;
beforeEach(inject(function(elementRegistry, canvas) {
taskShape_1 = elementRegistry.get('Task_1');
taskShape_2 = elementRegistry.get('Task_2');
subProcessShape_1 = elementRegistry.get('SubProcess_1');
rootShape = canvas.getRootElement();
}));
it('should not expand, if elements keep their parents (different original parents)', inject(function(modeling) {
// given
var originalBounds = getBounds(subProcessShape_1);
// when
modeling.moveElements([ taskShape_1, taskShape_2 ],
{ x: -100, y: 0 }, subProcessShape_1, { primaryShape: taskShape_1 });
// then
expect(subProcessShape_1).to.have.bounds(originalBounds);
}));
it('should expand non-primary parents', inject(function(modeling) {
// given
var originalBounds = getBounds(subProcessShape_1);
// when
modeling.moveElements([ taskShape_1, taskShape_2 ],
{ x: 100, y: 0 }, rootShape, { primaryShape: taskShape_2 });
// then
var expectedBounds = assign(originalBounds, { width: 525 });
expect(subProcessShape_1).to.have.bounds(expectedBounds);
}));
it('should expand, if elements keep their parents (same original parent)', inject(function(modeling) {
// given
var originalBounds = getBounds(subProcessShape_1);
modeling.moveElements([ taskShape_2 ], { x: -110, y: 135 }, subProcessShape_1);
// when
modeling.moveElements([ taskShape_1, taskShape_2 ],
{ x: -110, y: 0 }, subProcessShape_1, { primaryShape: taskShape_1 });
// then
var expectedBounds = assign(originalBounds, { x: 0, width: 444 });
expect(subProcessShape_1).to.have.bounds(expectedBounds);
}));
it('should expand, if primary shape changes parent', inject(function(modeling) {
// given
var originalBounds = getBounds(subProcessShape_1);
// when
modeling.moveElements([ taskShape_1, taskShape_2 ],
{ x: 0, y: 50 }, subProcessShape_1, { primaryShape: taskShape_2 });
// then
var expectedBounds = assign(originalBounds, { y: 80, height: 317 });
expect(subProcessShape_1).to.have.bounds(expectedBounds);
}));
it('should expand top and bottom edge, if primary shape changes parent', inject(function(modeling) {
// given
var originalBounds = getBounds(subProcessShape_1);
// when
modeling.moveElements([ taskShape_1, taskShape_2 ],
{ x: 0, y: 100 }, subProcessShape_1, { primaryShape: taskShape_2 });
// then
var expectedBounds = assign(originalBounds, { y: 130, height: 334 });
expect(subProcessShape_1).to.have.bounds(expectedBounds);
}));
});
describe('nested sub processes', function() {
var diagramXML = require('./AutoResize.nested-sub-processes.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
it('should recursively expand parent element', inject(function(elementRegistry, modeling) {
var taskShape = elementRegistry.get('Task_1'),
subProcessShape_2 = elementRegistry.get('SubProcess_2'),
subProcessShape_3 = elementRegistry.get('SubProcess_3');
var originalBounds = getBounds(subProcessShape_2);
modeling.moveElements([ taskShape ], { x: 100, y: 0 }, subProcessShape_3);
var expectedBounds = assign(originalBounds, { width: 755 });
expect(subProcessShape_2).to.have.bounds(expectedBounds);
}));
it('should recursively expand last parent element', inject(function(elementRegistry, modeling) {
var taskShape = elementRegistry.get('Task_1'),
subProcessShape_1 = elementRegistry.get('SubProcess_1'),
subProcessShape_3 = elementRegistry.get('SubProcess_3');
var originalBounds = getBounds(subProcessShape_1);
modeling.moveElements([ taskShape ], { x: 100, y: 0 }, subProcessShape_3);
var expectedBounds = assign(originalBounds, { width: 875 });
expect(subProcessShape_1).to.have.bounds(expectedBounds);
}));
});
describe('space-tool', function() {
var diagramXML = require('./AutoResize.space-tool.bpmn');
var taskShape,
participantShape,
originalBounds;
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
beforeEach(inject(function(elementRegistry) {
taskShape = elementRegistry.get('Task_1');
participantShape = elementRegistry.get('Participant_1');
originalBounds = getBounds(participantShape);
}));
it('should not expand after space-tool', inject(function(modeling) {
// given
var delta = { x: 50, y: 0 },
direction = 'e';
// when
modeling.createSpace([ taskShape ], [], delta, direction);
// then
var newBounds = getBounds(participantShape);
expect(originalBounds).to.eql(newBounds);
}));
});
});
================================================
FILE: test/spec/features/context-pad/ContextPad.activation.bpmn
================================================
simple text annotation
SequenceFlow_1
SequenceFlow_1
================================================
FILE: test/spec/features/context-pad/ContextPadProviderSpec.js
================================================
import {
bootstrapModeler,
getBpmnJS,
inject
} from 'test/TestHelper';
import TestContainer from 'mocha-test-container-support';
import {
query as domQuery,
queryAll as domQueryAll
} from 'min-dom';
import {
is
} from 'lib/util/ModelUtil';
import {
createCanvasEvent as canvasEvent
} from '../../../util/MockEvents';
import contextPadModule from 'lib/features/context-pad';
import coreModule from 'lib/core';
import modelingModule from 'lib/features/modeling';
import replaceMenuModule from 'lib/features/popup-menu';
import createModule from 'diagram-js/lib/features/create';
import customRulesModule from '../../../util/custom-rules';
import autoPlaceModule from 'lib/features/auto-place';
import appendMenuProvider from 'lib/features/popup-menu';
describe('features - context-pad', function() {
var testModules = [
coreModule,
modelingModule,
contextPadModule,
replaceMenuModule,
customRulesModule,
createModule,
appendMenuProvider
];
describe('remove action rules', function() {
var diagramXML = require('../../../fixtures/bpmn/simple.bpmn');
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
var deleteAction;
beforeEach(inject(function(contextPad) {
deleteAction = function(target) {
return padEntry(contextPad.getPad(target).html, 'delete');
};
}));
it('should add delete action by default', inject(function(elementRegistry, contextPad) {
// given
var element = elementRegistry.get('StartEvent_1');
// when
contextPad.open(element);
// then
expect(deleteAction(element)).to.exist;
}));
it('should add delete action to elements label by default', inject(function(elementRegistry, contextPad) {
// given
var element = elementRegistry.get('StartEvent_1');
var label = element.label;
// when
contextPad.open(label);
// then
expect(deleteAction(label)).to.exist;
}));
it('should include delete action when rule returns true',
inject(function(elementRegistry, contextPad, customRules) {
// given
customRules.addRule('elements.delete', 1500, function() {
return true;
});
var element = elementRegistry.get('StartEvent_1');
// when
contextPad.open(element);
// then
expect(deleteAction(element)).to.exist;
})
);
it('should NOT include delete action when rule returns false',
inject(function(elementRegistry, contextPad, customRules) {
// given
customRules.addRule('elements.delete', 1500, function() {
return false;
});
var element = elementRegistry.get('StartEvent_1');
// when
contextPad.open(element);
// then
expect(deleteAction(element)).not.to.exist;
})
);
it('should call rules with [ element ]', inject(function(elementRegistry, contextPad, customRules) {
// given
var element = elementRegistry.get('StartEvent_1');
customRules.addRule('elements.delete', 1500, function(context) {
// element array is actually passed
expect(context.elements).to.eql([ element ]);
return true;
});
// then
expect(function() {
contextPad.open(element);
}).not.to.throw();
}));
it('should include delete action when [ element ] is returned from rule',
inject(function(elementRegistry, contextPad, customRules) {
// given
customRules.addRule('elements.delete', 1500, function(context) {
return context.elements;
});
var element = elementRegistry.get('StartEvent_1');
// when
contextPad.open(element);
// then
expect(deleteAction(element)).to.exist;
})
);
it('should NOT include delete action when [ ] is returned from rule',
inject(function(elementRegistry, contextPad, customRules) {
// given
customRules.addRule('elements.delete', 1500, function() {
return [];
});
var element = elementRegistry.get('StartEvent_1');
// when
contextPad.open(element);
// then
expect(deleteAction(element)).not.to.exist;
})
);
describe('multi-element', function() {
it('should add delete action by default', inject(
function(elementRegistry, contextPad) {
// given
var event = elementRegistry.get('StartEvent_1'),
task = elementRegistry.get('Task_1');
// when
contextPad.open([ event, task ]);
// then
expect(deleteAction([ event, task ])).to.exist;
}
));
it('should NOT add delete action when rule returns false', inject(
function(elementRegistry, contextPad, customRules) {
// given
customRules.addRule('elements.delete', 1500, function() {
return false;
});
var event = elementRegistry.get('StartEvent_1'),
task = elementRegistry.get('Task_1');
// when
contextPad.open([ event, task ]);
// then
expect(deleteAction([ event, task ])).not.to.exist;
}
));
it('should trigger batch delete', inject(
function(elementRegistry, contextPad, customRules) {
// given
var event = elementRegistry.get('StartEvent_1'),
task = elementRegistry.get('Task_1');
contextPad.open([ event, task ]);
// when
contextPad.trigger('click', padEvent('delete'));
// then
expect(elementRegistry.get('StartEvent_1')).not.to.exist;
expect(elementRegistry.get('Task_1')).not.to.exist;
}
));
});
});
describe('available entries', function() {
var diagramXML = require('./ContextPad.activation.bpmn');
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
function expectContextPadEntries(elementOrId, expectedEntries) {
getBpmnJS().invoke(function(elementRegistry, contextPad) {
var element = typeof elementOrId === 'string' ? elementRegistry.get(elementOrId) : elementOrId;
contextPad.open(element, true);
var entries = contextPad._current.entries;
expectedEntries.forEach(function(name) {
if (name.charAt(0) === '!') {
name = name.substring(1);
expect(entries).not.to.have.property(name);
} else {
expect(entries).to.have.property(name);
}
});
});
}
it('should provide Task entries', inject(function() {
expectContextPadEntries('Task_1', [
'connect',
'replace',
'append.end-event',
'append.gateway',
'append.append-task',
'append.intermediate-event',
'append.text-annotation'
]);
}));
it('should provide EventBasedGateway entries', inject(function() {
expectContextPadEntries('EventBasedGateway_1', [
'connect',
'replace',
'append.receive-task',
'append.message-intermediate-event',
'append.timer-intermediate-event',
'append.condition-intermediate-event',
'append.signal-intermediate-event',
'append.text-annotation',
'!append.task'
]);
}));
it('should provide EndEvent entries', inject(function() {
expectContextPadEntries('EndEvent_1', [
'connect',
'replace',
'!append.task'
]);
}));
it('should provide Compensation Activity entries', inject(function() {
expectContextPadEntries('Task_2', [
'connect',
'replace',
'!append.end-event',
'append.text-annotation'
]);
}));
it('should provide Compensate Boundary entries', inject(function() {
expectContextPadEntries('BoundaryEvent_1', [
'connect',
'replace',
'append.compensation-activity',
'!append.end-event'
]);
}));
it('should provide DataStoreReference entries', inject(function() {
expectContextPadEntries('DataStoreReference', [
'connect',
'append.text-annotation',
'replace',
'!append.end-event'
]);
}));
it('should provide DataObjectReference entries', inject(function() {
expectContextPadEntries('DataObjectReference', [
'connect',
'append.text-annotation',
'replace',
'!append.end-event'
]);
}));
it('should provide Group entries', inject(function() {
expectContextPadEntries('Group_1', [
'append.text-annotation',
'delete',
'!replace'
]);
}));
it('should provide Text Annotation entries', inject(function() {
expectContextPadEntries('TextAnnotation_1', [
'connect',
'delete',
'!replace',
'!append.text-annotation'
]);
}));
it('should provide SequenceFlow entries', inject(function() {
expectContextPadEntries('SequenceFlow_1', [
'append.text-annotation',
'delete',
'replace',
'!connect'
]);
}));
it('should provide MessageFlow entries', inject(function() {
expectContextPadEntries('MessageFlow_1', [
'append.text-annotation',
'delete',
'!replace',
'!connect'
]);
}));
});
describe('create', function() {
var diagramXML = require('../../../fixtures/bpmn/simple.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
it('should attach boundary event', inject(function(dragging, contextPad, elementRegistry) {
// given
var task = elementRegistry.get('Task_1');
// when
contextPad.open(task);
contextPad.trigger('dragstart', padEvent('append.intermediate-event'));
dragging.move(canvasEvent({ x: task.x, y: task.y }));
dragging.hover({ element: task });
dragging.move(canvasEvent({ x: task.x + 80, y: task.y + 70 }));
dragging.end();
// then
expect(task.attachers).to.have.length(1);
}));
it('should attach boundary event to other target', inject(
function(dragging, contextPad, elementRegistry) {
// given
var task = elementRegistry.get('Task_1');
var subProcess = elementRegistry.get('SubProcess_1');
// when
contextPad.open(task);
contextPad.trigger('dragstart', padEvent('append.intermediate-event'));
dragging.move(canvasEvent({ x: subProcess.x, y: subProcess.y }));
dragging.hover({ element: subProcess });
dragging.move(canvasEvent({ x: subProcess.x + 80, y: subProcess.y + 5 }));
dragging.end();
// then
expect(subProcess.attachers).to.have.length(1);
})
);
it('should append gateway with marker', inject(
function(dragging, contextPad, elementRegistry) {
// given
var task = elementRegistry.get('Task_1');
// when
contextPad.open(task);
contextPad.trigger('dragstart', padEvent('append.gateway'));
dragging.move(canvasEvent({ x: task.x, y: task.y }));
dragging.hover({ element: task });
dragging.move(canvasEvent({ x: task.x + task.width + 30, y: task.y }));
var context = dragging.context(),
elements = context.data.elements;
dragging.end();
// then
expect(elements).to.have.length(1);
expect(is(elements[0], 'bpmn:ExclusiveGateway')).to.be.true;
expect(elements[0].di.isMarkerVisible).to.be.true;
})
);
describe('drop onto sub-process', function() {
var basicTests = [
{ action: 'append.append-task', expectedElement: 'bpmn:Task' },
{ action: 'append.intermediate-event', expectedElement: 'bpmn:IntermediateThrowEvent' }
];
for (const { action, expectedElement, iit = it } of basicTests) {
iit(`should create ${expectedElement}`, inject(function(dragging, contextPad, elementRegistry) {
// given
var subProcess = elementRegistry.get('SubProcess_1');
// when
contextPad.open(subProcess);
contextPad.trigger('dragstart', padEvent(action));
dragging.move(canvasEvent({ x: subProcess.x, y: subProcess.y }));
dragging.hover({ element: subProcess });
dragging.move(canvasEvent({ x: subProcess.x + 200, y: subProcess.y + 70 }));
dragging.end();
// then
// find new, unnamed element
var newElement = subProcess.children.find(element => {
return (
is(element, expectedElement) &&
!element.businessObject.name
);
});
expect(newElement, `element of type ${expectedElement}`).to.exist;
expect(newElement.incoming, 'incoming connections').to.be.empty;
}));
}
});
});
describe('replace', function() {
var diagramXML = require('../../../fixtures/bpmn/simple.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
it('should show popup menu in the correct position', inject(function(elementRegistry, contextPad) {
// given
var element = elementRegistry.get('StartEvent_1'),
padding = { y: 6, x: 1 },
padMenuRect,
replaceMenuRect;
contextPad.open(element);
// when
contextPad.trigger('click', padEvent('replace'));
padMenuRect = contextPad.getPad(element).html.getBoundingClientRect();
replaceMenuRect = getPopupMenu().getBoundingClientRect();
// then
expect(replaceMenuRect.left).to.be.at.most(padMenuRect.left + padding.x);
expect(replaceMenuRect.top).to.be.at.most(padMenuRect.bottom + padding.y);
}));
it('should hide wrench if replacement is disallowed', inject(
function(elementRegistry, contextPad, customRules) {
// given
var element = elementRegistry.get('StartEvent_1');
// disallow replacement
customRules.addRule('shape.replace', function(context) {
return !is(context.element, 'bpmn:StartEvent');
});
// when
contextPad.open(element);
var padNode = contextPad.getPad(element).html;
// then
expect(padEntry(padNode, 'replace')).not.to.exist;
}
));
it('should show wrench if replacement is allowed', inject(
function(elementRegistry, contextPad, customRules) {
// given
var element = elementRegistry.get('EndEvent_1');
// disallow replacement
customRules.addRule('shape.replace', function(context) {
return !is(context.element, 'bpmn:StartEvent');
});
// when
contextPad.open(element);
var padNode = contextPad.getPad(element).html;
// then
expect(padEntry(padNode, 'replace')).to.exist;
}
));
describe('create + ', function() {
it('should open replace', inject(
function(create, dragging, canvas, elementFactory, popupMenu) {
// given
var rootShape = canvas.getRootElement(),
startEvent = elementFactory.createShape({ type: 'bpmn:StartEvent' });
// when
create.start(canvasEvent({ x: 0, y: 0 }), startEvent);
dragging.move(canvasEvent({ x: 50, y: 50 }));
dragging.hover({ element: rootShape });
dragging.move(canvasEvent({ x: 75, y: 75 }));
dragging.end(canvasEvent({ x: 75, y: 75 }, { ctrlKey: true, metaKey: true }));
// then
expect(popupMenu.isOpen()).to.be.true;
}
));
it('should open boundary event replace menu', inject(
function(create, dragging, canvas, elementFactory, modeling, popupMenu) {
// given
var rootShape = canvas.getRootElement();
var task = elementFactory.createShape({ type: 'bpmn:Task' });
var intermediateEvent = elementFactory.createShape({ type: 'bpmn:IntermediateThrowEvent' });
modeling.createShape(task, { x: 100, y: 100 }, rootShape);
// when
create.start(canvasEvent({ x: 0, y: 0 }), intermediateEvent);
dragging.move(canvasEvent({ x: 50, y: 50 }));
dragging.hover({ element: task });
dragging.move(canvasEvent({ x: 50, y: 65 }));
dragging.end(canvasEvent({ x: 50, y: 65 }, { ctrlKey: true, metaKey: true }));
// then
var replaceMenuEntries = domQueryAll('[data-id$="-boundary"]', getPopupMenu());
expect(replaceMenuEntries).to.have.length(12);
}
));
it('should not open non-existing replace menu', inject(
function(create, dragging, canvas, elementFactory) {
// given
var rootShape = canvas.getRootElement(),
group = elementFactory.createShape({ type: 'bpmn:Group' }),
replaceMenu;
// when
create.start(canvasEvent({ x: 0, y: 0 }), group);
dragging.move(canvasEvent({ x: 50, y: 50 }));
dragging.hover({ element: rootShape });
dragging.move(canvasEvent({ x: 300, y: 300 }));
dragging.end(canvasEvent({ x: 300, y: 300 }, { ctrlKey: true, metaKey: true }));
replaceMenu = domQuery('.bpmn-replace', getPopupMenu());
// then
expect(replaceMenu).not.to.exist;
}
));
it('should NOT open replace menu if context pad NOT open', inject(
function(canvas, create, dragging, elementFactory) {
// given
var rootShape = canvas.getRootElement(),
startEvent = elementFactory.createShape({ type: 'bpmn:StartEvent' }),
task = elementFactory.createShape({ type: 'bpmn:Task' });
// when
create.start(canvasEvent({ x: 0, y: 0 }), [ startEvent, task ]);
dragging.move(canvasEvent({ x: 50, y: 50 }));
dragging.hover({ element: rootShape });
dragging.move(canvasEvent({ x: 75, y: 75 }));
dragging.end(canvasEvent({ x: 75, y: 75 }, { ctrlKey: true, metaKey: true }));
// then
var replaceMenu = getPopupMenu();
expect(replaceMenu).not.to.exist;
}
));
});
});
describe('auto place', function() {
var diagramXML = require('../../../fixtures/bpmn/simple.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules.concat(autoPlaceModule)
}));
it('should trigger', inject(function(elementRegistry, contextPad) {
// given
var element = elementRegistry.get('Task_1');
contextPad.open(element);
// mock event
var event = padEvent('append.gateway');
// when
contextPad.trigger('click', event);
// then
expect(element.outgoing).to.have.length(1);
}));
});
describe('disabled auto-place', function() {
var diagramXML = require('../../../fixtures/bpmn/simple.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules.concat(autoPlaceModule),
contextPad: {
autoPlace: false
}
}));
var container;
beforeEach(function() {
container = TestContainer.get(this);
});
it('should default to drag start', inject(function(elementRegistry, contextPad, dragging) {
// given
var element = elementRegistry.get('Task_1');
contextPad.open(element);
// mock event
var event = {
clientX: 100,
clientY: 100,
target: padEntry(container, 'append.gateway'),
preventDefault: function() {}
};
// when
contextPad.trigger('click', event);
// then
expect(dragging.context()).to.exist;
}));
});
describe('preview', function() {
var diagramXML = require('../../../fixtures/bpmn/simple.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules.concat(autoPlaceModule)
}));
it('should preview append', inject(function(canvas, elementRegistry, contextPad) {
// given
var element = elementRegistry.get('Task_1');
contextPad.open(element);
// mock event
var event = padEvent('append.gateway');
// when
contextPad.trigger('hover', event);
// then
expect(canvas.getLayer('complex-preview')).to.exist;
expect(domQueryAll('.djs-dragger', canvas.getLayer('complex-preview'))).to.have.length(2);
}));
it('should remove append preview on close', inject(function(canvas, elementRegistry, contextPad) {
// given
var element = elementRegistry.get('Task_1');
contextPad.open(element);
// mock event
var event = padEvent('append.gateway');
contextPad.trigger('hover', event);
expect(canvas.getLayer('complex-preview')).to.exist;
expect(domQueryAll('.djs-dragger', canvas.getLayer('complex-preview'))).to.have.length(2);
// when
contextPad.close();
// then
expect(domQueryAll('.djs-dragger', canvas.getLayer('complex-preview'))).to.have.length(0);
}));
});
});
function padEntry(element, name) {
return domQuery('[data-action="' + name + '"]', element);
}
function padEvent(entry) {
return getBpmnJS().invoke(function(canvas) {
var target = padEntry(canvas.getContainer(), entry);
return {
target: target,
preventDefault: function() {},
clientX: 100,
clientY: 100
};
});
}
function getPopupMenu() {
const popup = getBpmnJS().get('popupMenu');
return popup._current && domQuery('.djs-popup', popup._current.container);
}
================================================
FILE: test/spec/features/copy-paste/BpmnCopyPasteSpec.js
================================================
import {
bootstrapModeler,
getBpmnJS,
inject
} from 'test/TestHelper';
import bpmnCopyPasteModule from 'lib/features/copy-paste';
import copyPasteModule from 'diagram-js/lib/features/copy-paste';
import coreModule from 'lib/core';
import modelingModule from 'lib/features/modeling';
import camundaPackage from 'camunda-bpmn-moddle/resources/camunda.json';
import {
find,
forEach,
isArray,
isNumber,
keys,
map,
pick,
reduce
} from 'min-dash';
import {
getBusinessObject,
getDi,
is
} from 'lib/util/ModelUtil';
import { isRoot } from 'diagram-js/lib/util/ModelUtil';
/**
* @typedef {import('../../../../lib/model/Types').Element} Element
*/
describe('features/copy-paste', function() {
var testModules = [
bpmnCopyPasteModule,
copyPasteModule,
coreModule,
modelingModule
];
var basicXML = require('./basic.bpmn'),
copyPropertiesXML = require('./copy-properties.bpmn'),
propertiesXML = require('./properties.bpmn'),
complexXML = require('./complex.bpmn'),
collaborationXML = require('./collaboration.bpmn'),
collaborationMultipleXML = require('./collaboration-multiple.bpmn'),
collaborationAssociationsXML = require('./data-associations.bpmn'),
eventBasedGatewayXML = require('./event-based-gateway.bpmn'),
collapsedSubprocessXML = require('./collapsed-subprocess.bpmn'),
nestedSubprocessAnnotationsXML = require('./nested-subprocess-annotations.bpmn');
describe('basic diagram', function() {
beforeEach(bootstrapModeler(basicXML, {
modules: testModules
}));
describe('copy', function() {
it('should copy sub process', inject(function() {
// when
var tree = copy('SubProcess_1');
// then
expect(keys(tree)).to.have.length(3);
expect(getAllElementsInTree(tree, 0)).to.have.length(1);
expect(getAllElementsInTree(tree, 1)).to.have.length(3);
expect(getAllElementsInTree(tree, 2)).to.have.length(12);
expect(findDescriptorInTree('SubProcess_1', tree).isExpanded).to.be.true;
}));
describe('should copy boundary events without host', function() {
it('should copy/paste', inject(function(elementRegistry, canvas, copyPaste) {
// given
var boundaryEvent = elementRegistry.get('BoundaryEvent_1'),
rootElement = canvas.getRootElement();
// when
copyPaste.copy(boundaryEvent);
var copiedElements = copyPaste.paste({
element: rootElement,
point: {
x: 1000,
y: 1000
}
});
// then
expect(rootElement.children).to.have.length(2);
expect(copiedElements).to.have.length(1);
expect(copiedElements[0].type).to.eql('bpmn:IntermediateCatchEvent');
expect(copiedElements[0].attachedToRef).to.be.undefined;
expect(copiedElements[0].host).to.be.undefined;
expect(copiedElements[0].id).not.to.eql(boundaryEvent.id);
}));
it('should copy/paste and reattach', inject(function(elementRegistry, canvas, copyPaste) {
// given
var boundaryEvent = elementRegistry.get('BoundaryEvent_1'),
task = elementRegistry.get('Task_1'),
rootElement = canvas.getRootElement();
// when
copyPaste.copy(boundaryEvent);
var copiedElement = copyPaste.paste({
element: rootElement,
point: {
x: 1000,
y: 1000
}
})[0];
copyPaste.copy(copiedElement);
var attachedBoundaryEvent = copyPaste.paste({
element: task,
point: {
x: task.x,
y: task.y
},
hints: {
attach: 'attach'
}
})[0];
// then
expect(attachedBoundaryEvent.businessObject.attachedToRef).to.eql(task.businessObject);
expect(attachedBoundaryEvent.host).to.be.eql(task);
expect(attachedBoundaryEvent.type).to.eql('bpmn:BoundaryEvent');
}));
});
it('should not mutate copy', inject(function(copyPaste, elementRegistry, modeling) {
// given
var parent = elementRegistry.get('Process_1'),
event = elementRegistry.get('IntermediateThrowEvent_1');
copyPaste.copy(event);
// when
modeling.updateProperties(event, {
name: 'foo'
});
var elements = copyPaste.paste({
element: parent,
point: {
x: 1000,
y: 1000
}
});
// then
var pastedEvent = find(elements, function(element) {
return is(element, 'bpmn:IntermediateThrowEvent');
});
var pastedEventBo = getBusinessObject(pastedEvent);
expect(pastedEventBo.name).not.to.exist;
}));
});
it('should paste twice', inject(function(elementRegistry, canvas, copyPaste) {
// given
var subProcess = elementRegistry.get('SubProcess_1'),
rootElement = canvas.getRootElement();
// when
copyPaste.copy(subProcess);
copyPaste.paste({
element: rootElement,
point: {
x: 1000,
y: 1000
}
});
var elements = copyPaste.paste({
element: rootElement,
point: {
x: 2000,
y: 1000
}
});
// then
expect(rootElement.children).to.have.length(13);
var subProcesses = elements.filter(function(element) {
return is(element, 'bpmn:SubProcess');
});
expect(subProcesses[0].id).not.to.equal(subProcesses[1].id);
expect(subProcesses[0].businessObject).not.to.equal(subProcesses[1].businessObject);
}));
describe('integration', function() {
it('should copy conditionExpression and default flow properties',
inject(function(canvas, copyPaste, elementRegistry, modeling) {
// given
var subProcess = elementRegistry.get('SubProcess_1'),
rootElement = canvas.getRootElement();
// when
copyPaste.copy(subProcess);
modeling.removeShape(subProcess);
var elements = copyPaste.paste({
element: rootElement,
point: {
x: 300,
y: 300
}
});
// then
var task = find(elements, function(element) {
return is(element, 'bpmn:Task');
});
var taskBo = getBusinessObject(task);
var conditionalFlow = find(elementRegistry.getAll(), function(element) {
return is(element, 'bpmn:SequenceFlow') && element.businessObject.conditionExpression;
});
var defaultFlow = find(elementRegistry.getAll(), function(element) {
return is(element, 'bpmn:SequenceFlow') && taskBo.default.id === element.id;
});
expect(conditionalFlow).to.exist;
expect(defaultFlow).to.exist;
expect(Object.prototype.propertyIsEnumerable.call(taskBo, 'default')).to.be.false;
})
);
it('should copy attachedToRef properties', inject(function(canvas, copyPaste, elementRegistry) {
// given
var task = elementRegistry.get('Task_1'),
boundaryEvent = elementRegistry.get('BoundaryEvent_1'),
rootElement = canvas.getRootElement();
// when
copyPaste.copy([ task, boundaryEvent ]);
var elements = copyPaste.paste({
element: rootElement,
point: {
x: 1000,
y: 1000
}
});
// then
task = find(elements, function(element) {
return is(element, 'bpmn:Task');
});
boundaryEvent = find(elements, function(element) {
return is(element, 'bpmn:BoundaryEvent');
});
// then
var boundaryEventBo = getBusinessObject(boundaryEvent);
expect(boundaryEventBo.attachedToRef).to.equal(getBusinessObject(task));
}));
it('should copy loopCharacteristics properties',
inject(function(canvas, copyPaste, elementRegistry, modeling) {
// given
var subProcess = elementRegistry.get('SubProcess_2'),
rootElement = canvas.getRootElement();
// when
copyPaste.copy(subProcess);
modeling.removeShape(subProcess);
var elements = copyPaste.paste({
element: rootElement,
point: {
x: 300,
y: 300
}
});
subProcess = find(elements, function(element) {
return is(element, 'bpmn:SubProcess');
});
var subProcessesBo = getBusinessObject(subProcess);
var loopCharacteristics = subProcessesBo.loopCharacteristics;
expect(loopCharacteristics.$type).to.equal('bpmn:MultiInstanceLoopCharacteristics');
expect(loopCharacteristics.isSequential).to.be.true;
})
);
it('should copy color properties',
inject(function(canvas, copyPaste, elementRegistry, modeling) {
// given
var task = elementRegistry.get('Task_1'),
rootElement = canvas.getRootElement(),
fill = '#ff0000',
stroke = '#00ff00';
// when
modeling.setColor(task, { fill: fill, stroke: stroke });
copyPaste.copy(task);
var elements = copyPaste.paste({
element: rootElement,
point: {
x: 1000,
y: 1000
}
});
// then
task = find(elements, function(element) {
return is(element, 'bpmn:Task');
});
var di = getDi(task);
expect(di.get('background-color')).to.equal(fill);
expect(di.get('border-color')).to.equal(stroke);
// TODO @barmac: remove when we drop bpmn.io properties
expect(di.fill).to.equal(fill);
expect(di.stroke).to.equal(stroke);
})
);
it('should copy label', inject(
function(canvas, copyPaste, elementRegistry) {
// given
var startEvent = elementRegistry.get('StartEvent_1'),
rootElement = canvas.getRootElement();
copyPaste.copy(startEvent);
// when
var elements = copyPaste.paste({
element: rootElement,
point: {
x: 50,
y: 50
}
});
// then
expect(elements).to.have.length(2);
var startEventCopy = find(elements, function(element) {
return is(element, 'bpmn:StartEvent');
});
var startEventCopyBo = getBusinessObject(startEventCopy);
var startEventCopyDi = getDi(startEventCopy);
var startEventCopyLabel = startEventCopy.label;
expect(startEventCopyBo).to.exist;
expect(startEventCopyBo.name).to.equal('hello');
expect(startEventCopyDi).to.exist;
expect(startEventCopyLabel).to.exist;
expect(startEventCopyLabel.di).to.equal(startEventCopyDi);
expect(startEventCopyLabel.businessObject).to.equal(startEventCopyBo);
}
));
it('should copy name property', inject(
function(canvas, copyPaste, elementRegistry, modeling) {
// given
var startEvent = elementRegistry.get('StartEvent_1'),
rootElement = canvas.getRootElement();
copyPaste.copy(startEvent);
modeling.removeShape(startEvent);
// when
var elements = copyPaste.paste({
element: rootElement,
point: {
x: 300,
y: 300
}
});
// then
expect(elements).to.have.length(2);
startEvent = find(elements, function(element) {
return is(element, 'bpmn:StartEvent');
});
var startEventBo = getBusinessObject(startEvent);
expect(startEventBo.name).to.equal('hello');
}
));
it('should wire DIs correctly', inject(
function(canvas, copyPaste, elementRegistry) {
// given
var subprcoess = elementRegistry.get('SubProcess_1'),
rootElement = canvas.getRootElement();
copyPaste.copy(subprcoess);
// when
var elements = copyPaste.paste({
element: rootElement,
point: {
x: 300,
y: 300
}
});
// then
var subprocess = elements[0];
var di = subprocess.di;
expect(di).to.exist;
expect(di.bpmnElement).to.exist;
expect(di.bpmnElement).to.equal(subprocess.businessObject);
}
));
});
describe('rules', function() {
it('should allow copying boundary event without host', inject(function(elementRegistry) {
var boundaryEvent1 = elementRegistry.get('BoundaryEvent_1'),
boundaryEvent2 = elementRegistry.get('BoundaryEvent_2');
// when
var tree = copy([ boundaryEvent1, boundaryEvent2 ]);
expect(keys(tree)).to.have.length(1);
}));
});
});
describe('properties', function() {
beforeEach(bootstrapModeler(propertiesXML, { modules: testModules }));
function copyPasteElement(element) {
return getBpmnJS().invoke(function(canvas, copyPaste, elementRegistry, modeling) {
// given
element = elementRegistry.get(element);
var rootElement = canvas.getRootElement();
// when
copyPaste.copy(element);
modeling.removeShape(element);
return copyPaste.paste({
element: rootElement,
point: {
x: 1000,
y: 1000
}
});
});
}
it('should copy and paste non-interrupting boundary event', function() {
// when
var elements = copyPasteElement('SubProcess_NonInterrupting');
var subProcess = find(elements, function(element) {
return is(element, 'bpmn:SubProcess');
});
var boundaryEvent = subProcess.attachers[0],
boundaryEventBo = getBusinessObject(boundaryEvent);
// then
expect(boundaryEventBo.cancelActivity).to.be.false;
});
it('should copy and paste interrupting boundary event', function() {
// when
var elements = copyPasteElement('SubProcess_Interrupting');
var subProcess = find(elements, function(element) {
return is(element, 'bpmn:SubProcess');
});
var boundaryEvent = subProcess.attachers[0],
boundaryEventBo = getBusinessObject(boundaryEvent);
// then
expect(boundaryEventBo.cancelActivity).to.be.true;
});
it('should copy and paste event sub process', function() {
// when
var elements = copyPasteElement('SubProcess_Event');
var subProcess = find(elements, function(element) {
return is(element, 'bpmn:SubProcess');
});
var subProcessesBo = getBusinessObject(subProcess);
expect(subProcessesBo.triggeredByEvent).to.be.true;
expect(subProcessesBo.isExpanded).to.be.true;
});
it('should copy and paste transaction', function() {
// when
var elements = copyPasteElement('SubProcess_Transaction');
var transaction = find(elements, function(element) {
return is(element, 'bpmn:Transaction');
});
expect(transaction).to.exist;
});
it('should copy and paste group', function() {
// when
var elements = copyPasteElement('Group');
var group = find(elements, function(element) {
return is(element, 'bpmn:Group');
});
var groupBo = getBusinessObject(group);
expect(groupBo.categoryValueRef).to.exist;
});
});
describe('collaboration', function() {
beforeEach(bootstrapModeler(collaborationXML, { modules: testModules }));
describe('integration', function() {
it('expanded participant', integrationTest('Participant_1'));
it('collapsed participant', integrationTest('Participant_2'));
});
describe('rules', function() {
it('should NOT allow copying lanes without their parent participant', function() {
// when
var tree = copy([ 'Lane_1', 'Lane_2' ]);
// then
expect(keys(tree)).to.have.length(0);
});
});
});
describe('collaboration (multiple)', function() {
beforeEach(bootstrapModeler(collaborationMultipleXML, { modules: testModules }));
describe('basics', function() {
it('should paste onto lane', inject(function(copyPaste, elementRegistry) {
// given
var participant = elementRegistry.get('Participant_2'),
lane = elementRegistry.get('Lane_5'),
laneBo = getBusinessObject(lane),
task = elementRegistry.get('Task_1');
copyPaste.copy(task);
// when
copyPaste.paste({
element: lane,
point: {
x: 400,
y: 450
}
});
// then
expect(participant.children).to.have.length(7);
expect(lane.children).to.be.empty;
expect(laneBo.flowNodeRef).to.have.length(2);
}));
it('should paste onto nested lane', inject(function(copyPaste, elementRegistry) {
// given
var participant = elementRegistry.get('Participant_1'),
lane = elementRegistry.get('Lane_3'),
laneBo = getBusinessObject(lane),
task = elementRegistry.get('Task_2');
// when
copyPaste.copy(task);
copyPaste.paste({
element: lane,
point: {
x: 450,
y: 150
}
});
// then
expect(participant.children).to.have.length(5);
expect(lane.children).to.be.empty;
expect(lane.parent.children).to.have.length(2);
expect(laneBo.flowNodeRef).to.have.length(2);
}));
});
describe('integration', function() {
it('should copy and paste multiple participants', integrationTest([
'Participant_1',
'Participant_2'
]));
});
});
describe('participants', function() {
beforeEach(bootstrapModeler(collaborationAssociationsXML, {
modules: testModules,
moddleExtensions: {
camunda: camundaPackage
}
}));
function expectEqual(copy, original) {
var originalBo = getBusinessObject(original);
var copyBo = getBusinessObject(copy);
expect(originalBo).not.to.equal(copyBo);
var originalProcessRef = originalBo.processRef;
var copyProcessRef = copyBo.processRef;
expect(copyProcessRef).not.to.equal(originalProcessRef);
expect(copyProcessRef.extensionElements).to.exist;
expect(copyProcessRef.extensionElements.values).to.have.length(1);
expect(copyProcessRef.get('flowElements')).to.have.length(originalProcessRef.get('flowElements').length);
var copyExecutionListener = copyProcessRef.extensionElements.values[0];
var originalExtensionListener = originalProcessRef.extensionElements.values[0];
expect(copyExecutionListener.$type).to.equal(originalExtensionListener.$type);
expect(copyExecutionListener.class).to.equal(originalExtensionListener.class);
expect(copyExecutionListener.event).to.equal(originalExtensionListener.event);
}
it('should copy participant with process', inject(
function(canvas, copyPaste, elementRegistry) {
// given
var participantInput = elementRegistry.get('Participant_Input'),
participantOutput = elementRegistry.get('Participant_Output'),
rootElement = canvas.getRootElement();
// when
copyPaste.copy([ participantInput, participantOutput ]);
var elements_1 = copyPaste.paste({
element: rootElement,
point: {
x: 5000,
y: 5000
}
});
// then
var participants_1 = elements_1.filter(function(element) {
return is(element, 'bpmn:Participant');
});
expect(participants_1).to.have.length(2);
expectEqual(participantInput, participants_1[0]);
expectEqual(participantOutput, participants_1[1]);
// but when
// paste second time
var elements_2 = copyPaste.paste({
element: rootElement,
point: {
x: 7000,
y: 5000
}
});
// then
var participants_2 = elements_2.filter(function(element) {
return is(element, 'bpmn:Participant');
});
expect(participants_2).to.have.length(2);
expectEqual(participants_2[0], participantInput);
expectEqual(participants_2[1], participantOutput);
expectEqual(participants_2[0], participants_1[0]);
expectEqual(participants_2[1], participants_1[1]);
}
));
it('should copy and paste participant with DataInputAssociation',
integrationTest('Participant_Input'));
it('should copy and paste participant with DataOutputAssociation',
integrationTest('Participant_Output'));
});
describe('nested properties', function() {
beforeEach(bootstrapModeler(copyPropertiesXML, {
modules: testModules,
moddleExtensions: {
camunda: camundaPackage
}
}));
it('integration', integrationTest('Participant_1'));
it('should copy user task properties', inject(function(copyPaste, elementRegistry) {
var participant = elementRegistry.get('Participant_1'),
task = elementRegistry.get('Task_1'),
taskBo = getBusinessObject(task);
// when
copyPaste.copy(task);
var elements = copyPaste.paste({
element: participant,
point: {
x: 500,
y: 50
}
});
// then
var newTask = find(elements, function(element) {
return is(element, 'bpmn:Task');
});
var newTaskBo = getBusinessObject(newTask);
expect(newTaskBo.asyncBefore).to.equal(taskBo.asyncBefore);
expect(newTaskBo.documentation).to.jsonEqual(taskBo.documentation);
expect(newTaskBo.extensionElements).to.jsonEqual(taskBo.extensionElements);
}));
});
describe('event based gateway', function() {
beforeEach(bootstrapModeler(eventBasedGatewayXML, {
modules: testModules
}));
it('should copy and paste event based gateway connected to an event', integrationTest([
'EventBasedGateway_1',
'IntermediateCatchEvent_1'
]));
});
describe('collapsed sub-process', function() {
beforeEach(bootstrapModeler(collapsedSubprocessXML, {
modules: testModules
}));
it('should paste with children', inject(
function(copyPaste, elementRegistry, modeling, bpmnjs) {
// given
var subProcess = elementRegistry.get('SUB_PROCESS'),
root = elementRegistry.get('PROCESS'),
definitions = bpmnjs.getDefinitions();
// when
copyPaste.copy(subProcess);
modeling.removeElements([ subProcess ]);
var pastedElements = copyPaste.paste({
element: root,
point: {
x: 500,
y: 50
}
});
// then
// elements pasted with original IDs
forEach([ 'SUB_PROCESS', 'SUB_TASK', 'SUB_BOUNDARY' ], function(id) {
var el = find(pastedElements, function(el) {
return el.id === id;
});
expect(el, 'element <' + id + '>').to.exist;
});
// referenced root element exists only once
var escalations = definitions.get('rootElements').filter(function(el) {
return el.$type === 'bpmn:Escalation';
});
expect(escalations).to.have.length(1);
}
));
});
describe('expanded sub-process', function() {
beforeEach(bootstrapModeler(nestedSubprocessAnnotationsXML, {
modules: testModules
}));
it('should paste with children', inject(
function(canvas, copyPaste, elementRegistry) {
// given
var subProcess = elementRegistry.get('SubProcess_1'),
nestedSubProcess = elementRegistry.get('SubProcess_2'),
rootElement = canvas.getRootElement();
// when
copyPaste.copy(subProcess);
var pastedElements = copyPaste.paste({
element: rootElement,
point: {
x: 800,
y: 200
}
});
// then
var pastedSubProcesses = pastedElements.filter(function(el) {
return is(el, 'bpmn:SubProcess');
});
var pastedParentSubProcess = pastedSubProcesses.find(function(el) {
return el.parent === rootElement;
});
var pastedNestedSubProcess = pastedSubProcesses.find(function(el) {
return el.parent === pastedParentSubProcess;
});
expect(pastedSubProcesses).to.have.length(2);
expect(pastedNestedSubProcess).to.exist;
expect(pastedNestedSubProcess.children).to.have.length(nestedSubProcess.children.length);
}
));
it('should paste with annotations', inject(
function(canvas, copyPaste, elementRegistry) {
// given
var subProcess = elementRegistry.get('SubProcess_1'),
rootElement = canvas.getRootElement();
// when
copyPaste.copy(subProcess);
var pastedElements = copyPaste.paste({
element: rootElement,
point: {
x: 800,
y: 200
}
});
// then
var pastedAnnotation = find(pastedElements, function(element) {
return is(element, 'bpmn:TextAnnotation');
});
var pastedAssociation = find(pastedElements, function(element) {
return is(element, 'bpmn:Association');
});
expect(pastedAnnotation).to.exist;
expect(pastedAssociation).to.exist;
expect(pastedAssociation.source).to.equal(
find(pastedElements, function(el) { return is(el, 'bpmn:Task'); })
);
expect(pastedAssociation.target).to.equal(pastedAnnotation);
}
));
});
describe('complex', function() {
beforeEach(bootstrapModeler(complexXML, {
modules: testModules,
moddleExtensions: {
camunda: camundaPackage
}
}));
it('should mark as changed', inject(
function(canvas, eventBus, copyPaste, elementRegistry, commandStack) {
// given
var participant = elementRegistry.get('sid-187453C6-5AB5-4A6D-9A62-BF537E04EA0D'),
rootElement = canvas.getRootElement();
var changedSpy = sinon.spy(function(event) {
expect(event.elements).to.have.length(56);
});
// when
eventBus.on('elements.changed', changedSpy);
copyPaste.copy([ participant ]);
copyPaste.paste({
element: rootElement,
point: {
x: 800,
y: 300
}
});
commandStack.undo();
commandStack.redo();
// then
expect(changedSpy).to.have.been.calledThrice;
}
));
});
});
// helpers //////////
/**
* Integration test involving copying, pasting, moving, undoing and redoing.
*
* @param {string|string[]} elementIds
*/
function integrationTest(elementIds) {
if (!isArray(elementIds)) {
elementIds = [ elementIds ];
}
return function() {
getBpmnJS().invoke(function(canvas, commandStack, copyPaste, elementRegistry, modeling) {
// given
var allElements = elementRegistry.getAll();
var initialContext = {
length: allElements.length,
ids: getPropertyForElements(allElements, 'id'),
types: getPropertyForElements(allElements, 'type')
},
currentContext;
var elements = map(elementIds, function(elementId) {
return elementRegistry.get(elementId);
});
// (1) copy elements
copyPaste.copy(elements);
// (2) remove elements
modeling.removeElements(elements);
var rootElement = canvas.getRootElement();
// (3) paste elements
copyPaste.paste({
element: rootElement,
point: {
x: 500,
y: 500
}
});
// (4) move all elements except root
modeling.moveElements(elementRegistry.filter(element => !isRoot(element)), { x: 50, y: -50 });
// when
// (5) undo moving, pasting and removing
commandStack.undo();
commandStack.undo();
commandStack.undo();
elements = elementRegistry.getAll();
currentContext = {
length: elements.length,
ids: getPropertyForElements(elements, 'id')
};
// then
expect(initialContext.length).to.equal(currentContext.length);
expectCollection(initialContext.ids, currentContext.ids, true);
// when
// (6) redo removing, pasting and moving
commandStack.redo();
commandStack.redo();
commandStack.redo();
elements = elementRegistry.getAll();
currentContext = {
length: elements.length,
ids: getPropertyForElements(elements, 'id'),
types: getPropertyForElements(elements, 'type')
};
// then
expect(initialContext.length).to.equal(currentContext.length);
expectCollection(initialContext.ids, currentContext.ids, false);
expectCollection(initialContext.types, currentContext.types, true);
});
};
}
function getPropertyForElements(elements, property) {
return map(elements, function(element) {
return element[ property ];
});
}
function expectCollection(collection1, collection2, contains) {
expect(collection1).to.have.length(collection2.length);
forEach(collection2, function(element) {
if (!element.parent) {
return;
}
if (contains) {
expect(collection1).to.contain(element);
} else {
expect(collection1).not.to.contain(element);
}
});
}
function getAllElementsInTree(tree, depth) {
var depths;
if (isNumber(depth)) {
depths = pick(tree, [ depth ]);
} else {
depths = tree;
}
return reduce(depths, function(allElements, depth) {
return allElements.concat(depth);
}, []);
}
function findDescriptorInTree(elements, tree, depth) {
var foundDescriptors = _findDescriptorsInTree(elements, tree, depth);
if (foundDescriptors.length !== 1) {
return false;
}
return foundDescriptors[0];
}
function _findDescriptorsInTree(elements, tree, depth) {
if (!isArray(elements)) {
elements = [ elements ];
}
var depths;
if (isNumber(depth)) {
depths = pick(tree, [ depth ]);
} else {
depths = tree;
}
return reduce(elements, function(foundDescriptors, element) {
var foundDescriptor = reduce(depths, function(foundDescriptor, depth) {
return foundDescriptor || find(depth, function(descriptor) {
return element === descriptor.id || element.id === descriptor.id;
});
});
if (foundDescriptor) {
return foundDescriptors.concat(foundDescriptor);
}
return foundDescriptors;
}, []);
}
/**
* Copy elements.
*
* @param {(string|Element)[]} elements
*
* @return {Object}
*/
function copy(elements) {
if (!isArray(elements)) {
elements = [ elements ];
}
return getBpmnJS().invoke(function(copyPaste, elementRegistry) {
elements = elements.map(function(element) {
element = elementRegistry.get(element.id || element);
expect(element).to.exist;
return element;
});
return copyPaste.copy(elements);
});
}
================================================
FILE: test/spec/features/copy-paste/ModdleCopySpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import copyPasteModule from 'lib/features/copy-paste';
import coreModule from 'lib/core';
import modelingModule from 'lib/features/modeling';
import camundaModdleModule from 'camunda-bpmn-moddle/lib';
import camundaPackage from 'camunda-bpmn-moddle/resources/camunda.json';
import {
getBusinessObject,
is
} from 'lib/util/ModelUtil';
var HIGH_PRIORITY = 3000;
describe('features/copy-paste/ModdleCopy', function() {
var testModules = [
camundaModdleModule,
copyPasteModule,
coreModule,
modelingModule
];
var basicXML = require('../../../fixtures/bpmn/basic.bpmn');
beforeEach(bootstrapModeler(basicXML, {
modules: testModules,
moddleExtensions: {
camunda: camundaPackage
}
}));
describe('simple', function() {
it('should copy primitive properties', inject(function(moddle, moddleCopy) {
// given
var userTask = moddle.create('bpmn:UserTask', {
asyncBefore: true
});
// when
var serviceTask = moddleCopy.copyElement(userTask, moddle.create('bpmn:ServiceTask'));
// then
expect(serviceTask.asyncBefore).to.be.true;
expectNoAttrs(serviceTask);
}));
it('should copy arrays of properties', inject(function(moddle, moddleCopy) {
// given
var messageEventDefinition = moddle.create('bpmn:MessageEventDefinition'),
signalEventDefinition = moddle.create('bpmn:SignalEventDefinition'),
startEvent = moddle.create('bpmn:StartEvent');
startEvent.eventDefinitions = [ messageEventDefinition, signalEventDefinition ];
// when
var endEvent = moddleCopy.copyElement(startEvent, moddle.create('bpmn:EndEvent'));
// then
var eventDefinitions = endEvent.eventDefinitions;
expect(eventDefinitions).to.have.length(2);
expect(eventDefinitions[0].$type).to.equal('bpmn:MessageEventDefinition');
expect(eventDefinitions[1].$type).to.equal('bpmn:SignalEventDefinition');
expectNoAttrs(endEvent);
}));
it('should NOT copy properties that are not allowed in target element', inject(
function(moddle, moddleCopy) {
// given
var userTask = moddle.create('bpmn:UserTask', {
assignee: 'foobar'
});
// when
var serviceTask = moddleCopy.copyElement(userTask, moddle.create('bpmn:ServiceTask'));
// then
expect(serviceTask.assignee).not.to.exist;
expectNoAttrs(serviceTask);
}
));
it('should NOT copy IDs if taken', inject(function(moddle, moddleCopy, canvas, modeling) {
// given
var task = modeling.createShape({ type: 'bpmn:Task' },
{ x: 0, y: 0 }, canvas.getRootElement());
var taskId = task.id;
// when
var userTask = moddleCopy.copyElement(task.businessObject, moddle.create('bpmn:UserTask'));
// then
expect(userTask.id).not.to.equal(taskId);
expectNoAttrs(userTask);
}));
it('should copy IDs if free', inject(function(moddle, moddleCopy, canvas, modeling) {
// given
var task = modeling.createShape({ type: 'bpmn:Task' },
{ x: 0, y: 0 }, canvas.getRootElement());
var taskId = task.id;
// when
modeling.removeShape(task);
var userTask = moddleCopy.copyElement(task.businessObject, moddle.create('bpmn:UserTask'));
// then
expect(userTask.id).to.equal(taskId);
expectNoAttrs(userTask);
}));
it('should NOT copy ', inject(function(moddle, moddleCopy) {
// given
var processElement = moddle.create('bpmn:Process'),
participant = moddle.create('bpmn:Participant');
participant.processRef = processElement;
// when
var copiedParticipant = moddleCopy.copyElement(participant, moddle.create('bpmn:Participant'));
// then
expect(copiedParticipant).not.to.equal(participant);
expect(copiedParticipant.processRef).not.to.exist;
}));
it('should NOT copy misc references', inject(function(moddle, moddleCopy) {
// given
var label = moddle.create('bpmndi:BPMNLabel'),
labelStyle = moddle.create('bpmndi:BPMNLabelStyle');
label.labelStyle = labelStyle;
// when
var copiedLabel = moddleCopy.copyElement(label, moddle.create('bpmndi:BPMNLabel'));
// then
expect(copiedLabel.labelStyle).not.to.exist;
}));
it('should copy extension elements last', inject(function(moddleCopy, eventBus, moddle) {
// given
var connector = moddle.create('camunda:Connector'),
extensionElements = moddle.create('bpmn:ExtensionElements'),
messageEventDefinition = moddle.create('bpmn:MessageEventDefinition'),
messageEndEvent = moddle.create('bpmn:EndEvent');
connector.$parent = extensionElements;
extensionElements.$parent = messageEventDefinition;
extensionElements.values = [ connector ];
messageEventDefinition.$parent = messageEndEvent;
messageEventDefinition.extensionElements = extensionElements;
messageEndEvent.eventDefinitions = [ messageEventDefinition ];
var propertyNames = [];
eventBus.on('moddleCopy.canCopyProperty', function(context) {
var propertyName = context.propertyName;
propertyNames.push(propertyName);
});
moddleCopy.copyElement(messageEndEvent, moddle.create('bpmn:EndEvent'), [
'extensionElements',
'name'
]);
expect(propertyNames).to.eql([
'name',
'extensionElements'
]);
}));
it('should NOT copy empty extension elements', inject(function(moddle, moddleCopy) {
// given
var connector = moddle.create('camunda:Connector'),
extensionElements = moddle.create('bpmn:ExtensionElements'),
messageEventDefinition = moddle.create('bpmn:MessageEventDefinition'),
messageEndEvent = moddle.create('bpmn:EndEvent');
connector.$parent = extensionElements;
extensionElements.$parent = messageEventDefinition;
extensionElements.values = [ connector ];
messageEventDefinition.$parent = messageEndEvent;
messageEventDefinition.extensionElements = extensionElements;
messageEndEvent.eventDefinitions = [ messageEventDefinition ];
var startEvent = moddleCopy.copyElement(messageEndEvent, moddle.create('bpmn:StartEvent'));
// connector not allowed in start event
expect(startEvent.eventDefinitions[0].extensionElements).not.to.exist;
}));
it('should only copy specified properties', inject(function(moddle, moddleCopy) {
// given
var userTask = moddle.create('bpmn:UserTask', {
asyncBefore: true,
name: 'foo'
});
// when
var serviceTask = moddleCopy.copyElement(
userTask,
moddle.create('bpmn:ServiceTask'),
'asyncBefore'
);
// then
expect(serviceTask.asyncBefore).to.be.true;
expect(serviceTask.name).not.to.exist;
expectNoAttrs(serviceTask);
}));
});
describe('nested', function() {
it('should copy documentation', inject(function(moddle, moddleCopy) {
// given
var documentation = [
moddle.create('bpmn:Documentation', { text: 'FOO\nBAR', textFormat: 'xyz' }),
moddle.create('bpmn:Documentation', { text: '' })
];
var userTask = moddle.create('bpmn:UserTask');
userTask.documentation = documentation;
// when
var serviceTask = moddleCopy.copyElement(userTask, moddle.create('bpmn:ServiceTask'));
expect(serviceTask.documentation[0].$parent).to.equal(serviceTask);
expect(serviceTask.documentation[0].text).to.equal('FOO\nBAR');
expect(serviceTask.documentation[0].textFormat).to.equal('xyz');
expect(serviceTask.documentation[1].$parent).to.equal(serviceTask);
expect(serviceTask.documentation[1].text).to.equal('');
}));
it('should copy execution listener', inject(function(moddle, moddleCopy) {
// given
var script = moddle.create('camunda:Script', {
scriptFormat: 'groovy',
value: 'foo = bar;'
});
var executionListener = moddle.create('camunda:ExecutionListener', {
event: 'start',
script: script
});
var extensionElements = moddle.create('bpmn:ExtensionElements'),
userTask = moddle.create('bpmn:UserTask');
executionListener.$parent = extensionElements;
extensionElements.$parent = userTask;
extensionElements.values = [ executionListener ];
userTask.extensionElements = extensionElements;
// when
var serviceTask = moddleCopy.copyElement(userTask, moddle.create('bpmn:ServiceTask'));
// then
executionListener = serviceTask.extensionElements.values[0];
expect(executionListener).to.exist;
expect(executionListener.$type).to.equal('camunda:ExecutionListener');
expect(executionListener.$parent).to.equal(serviceTask.extensionElements);
expect(executionListener.event).to.equal('start');
script = executionListener.script;
expect(script).to.exist;
expect(script.$type).to.equal('camunda:Script');
expect(script.$parent).to.equal(executionListener);
expect(script.scriptFormat).to.equal('groovy');
expect(script.value).to.equal('foo = bar;');
}));
it('should copy output parameter', inject(function(moddle, moddleCopy) {
// given
var outputParameter = moddle.create('camunda:OutputParameter', {
name: 'foo',
definition: moddle.create('camunda:List', {
items: [
moddle.create('camunda:Value', { value: '${1+1}' }),
moddle.create('camunda:Value', { value: '${1+2}' }),
moddle.create('camunda:Value', { value: '${1+3}' })
]
})
});
var inputOutput = moddle.create('camunda:InputOutput', {
outputParameters: [ outputParameter ]
});
var extensionElements = moddle.create('bpmn:ExtensionElements'),
userTask = moddle.create('bpmn:UserTask');
extensionElements.$parent = userTask;
extensionElements.values = [ inputOutput ];
userTask.extensionElements = extensionElements;
// when
var subProcess = moddleCopy.copyElement(userTask, moddle.create('bpmn:SubProcess'));
// then
extensionElements = subProcess.extensionElements;
inputOutput = extensionElements.values[0];
expect(inputOutput.$type).to.equal('camunda:InputOutput');
expect(inputOutput.$parent).to.equal(extensionElements);
outputParameter = inputOutput.outputParameters[0];
expect(outputParameter.$type).to.equal('camunda:OutputParameter');
expect(outputParameter.$parent).to.equal(inputOutput);
expect(outputParameter.name).to.equal('foo');
var definition = outputParameter.definition;
expect(definition.$type).to.equal('camunda:List');
expect(definition.$parent).to.equal(outputParameter);
var items = definition.items;
expect(items[0].$type).to.equal('camunda:Value');
expect(items[0].$parent).to.equal(definition);
expect(items[0].value).to.equal('${1+1}');
expect(items[1].$type).to.equal('camunda:Value');
expect(items[1].$parent).to.equal(definition);
expect(items[1].value).to.equal('${1+2}');
expect(items[2].$type).to.equal('camunda:Value');
expect(items[2].$parent).to.equal(definition);
expect(items[2].value).to.equal('${1+3}');
}));
});
describe('integration', function() {
describe('camunda:Connector', function() {
it('should copy if parent is message event definition and is child of end event', inject(
function(moddle, moddleCopy) {
// given
var connector = moddle.create('camunda:Connector', {
connectorId: 'foo'
});
var extensionElements = moddle.create('bpmn:ExtensionElements'),
messageEventDefinition = moddle.create('bpmn:MessageEventDefinition'),
messageIntermediateThrowEvent = moddle.create('bpmn:IntermediateThrowEvent');
connector.$parent = extensionElements;
extensionElements.$parent = messageEventDefinition;
extensionElements.values = [ connector ];
messageEventDefinition.$parent = messageIntermediateThrowEvent;
messageEventDefinition.extensionElements = extensionElements;
messageIntermediateThrowEvent.eventDefinitions = [ messageEventDefinition ];
// when
var endEvent =
moddleCopy.copyElement(messageIntermediateThrowEvent, moddle.create('bpmn:EndEvent'));
// then
extensionElements = endEvent.eventDefinitions[0].extensionElements;
expect(extensionElements.values[0].$type).to.equal('camunda:Connector');
expect(extensionElements.values[0].connectorId).to.equal('foo');
}
));
});
describe('camunda:Field', function() {
it('should copy if parent is message event definition and is child of end event', inject(
function(moddle, moddleCopy) {
// given
var field = moddle.create('camunda:Field', {
name: 'foo'
});
var extensionElements = moddle.create('bpmn:ExtensionElements'),
messageEventDefinition = moddle.create('bpmn:MessageEventDefinition'),
messageIntermediateThrowEvent = moddle.create('bpmn:IntermediateThrowEvent');
field.$parent = extensionElements;
extensionElements.$parent = messageEventDefinition;
extensionElements.values = [ field ];
messageEventDefinition.$parent = messageIntermediateThrowEvent;
messageEventDefinition.extensionElements = extensionElements;
messageIntermediateThrowEvent.eventDefinitions = [ messageEventDefinition ];
// when
var endEvent =
moddleCopy.copyElement(messageIntermediateThrowEvent, moddle.create('bpmn:EndEvent'));
// then
extensionElements = endEvent.eventDefinitions[0].extensionElements;
expect(extensionElements.values[0].$type).to.equal('camunda:Field');
expect(extensionElements.values[0].name).to.equal('foo');
}
));
});
describe('camunda:FailedJobRetryTimeCycle', function() {
it('should copy if parent is SignalEventDefinition and is intermediate throwing', inject(
function(moddle, moddleCopy) {
// given
var retryCycle = moddle.create('camunda:FailedJobRetryTimeCycle', {
body: 'foo'
});
var extensionElements = moddle.create('bpmn:ExtensionElements'),
signalEventDefinition = moddle.create('bpmn:SignalEventDefinition'),
signalIntermediateThrowEvent = moddle.create('bpmn:IntermediateThrowEvent');
retryCycle.$parent = extensionElements;
extensionElements.$parent = signalEventDefinition;
extensionElements.values = [ retryCycle ];
signalEventDefinition.$parent = signalIntermediateThrowEvent;
signalEventDefinition.extensionElements = extensionElements;
signalIntermediateThrowEvent.eventDefinitions = [ signalEventDefinition ];
// when
var intermediateThrowEvent = moddleCopy.copyElement(
signalIntermediateThrowEvent,
moddle.create('bpmn:IntermediateThrowEvent')
);
// then
extensionElements = intermediateThrowEvent.eventDefinitions[0].extensionElements;
expect(extensionElements.values[0].$type).to.equal('camunda:FailedJobRetryTimeCycle');
expect(extensionElements.values[0].body).to.equal('foo');
}
));
it('should copy if parent is TimerEventDefinition and is catching', inject(
function(moddle, moddleCopy) {
// given
var retryCycle = moddle.create('camunda:FailedJobRetryTimeCycle', {
body: 'foo'
});
var extensionElements = moddle.create('bpmn:ExtensionElements'),
timerEventDefinition = moddle.create('bpmn:TimerEventDefinition'),
timerStartEvent = moddle.create('bpmn:StartEvent');
retryCycle.$parent = extensionElements;
extensionElements.$parent = timerEventDefinition;
extensionElements.values = [ retryCycle ];
timerEventDefinition.$parent = timerStartEvent;
timerEventDefinition.extensionElements = extensionElements;
timerStartEvent.eventDefinitions = [ timerEventDefinition ];
// when
var intermediateCatchEvent =
moddleCopy.copyElement(timerStartEvent, moddle.create('bpmn:IntermediateCatchEvent'));
// then
extensionElements = intermediateCatchEvent.eventDefinitions[0].extensionElements;
expect(extensionElements.values[0].$type).to.equal('camunda:FailedJobRetryTimeCycle');
expect(extensionElements.values[0].body).to.equal('foo');
}
));
it('should copy if parent is call activity', inject(function(moddle, moddleCopy) {
// given
var retryCycle = moddle.create('camunda:FailedJobRetryTimeCycle', {
body: 'foo'
});
var extensionElements = moddle.create('bpmn:ExtensionElements'),
loopCharacteristics = moddle.create('bpmn:MultiInstanceLoopCharacteristics'),
subProcess = moddle.create('bpmn:SubProcess');
retryCycle.$parent = extensionElements;
extensionElements.$parent = loopCharacteristics;
extensionElements.values = [ retryCycle ];
loopCharacteristics.$parent = subProcess;
loopCharacteristics.extensionElements = extensionElements;
subProcess.loopCharacteristics = loopCharacteristics;
// when
var callActivity = moddleCopy.copyElement(subProcess, moddle.create('bpmn:CallActivity'));
// then
extensionElements = callActivity.loopCharacteristics.extensionElements;
expect(extensionElements.values[0].$type).to.equal('camunda:FailedJobRetryTimeCycle');
expect(extensionElements.values[0].body).to.equal('foo');
}));
});
describe('generic properties', function() {
it('should not copy generic extension elements', inject(function(moddle, moddleCopy) {
// given
var genericExtensionElement = moddle.createAny('foo:property', {
value: 'foo'
});
var extensionElements = moddle.create('bpmn:ExtensionElements'),
startEvent = moddle.create('bpmn:StartEvent');
genericExtensionElement.$parent = extensionElements;
extensionElements.$parent = startEvent;
extensionElements.values = [ genericExtensionElement ];
startEvent.extensionElements = extensionElements;
// when
var endEvent = moddleCopy.copyElement(startEvent, moddle.create('bpmn:EndEvent'));
// then
expect(endEvent.extensionElements).not.to.exist;
}));
});
});
describe('events', function() {
it('should disallow copying properties', inject(function(moddleCopy, eventBus, moddle) {
// given
var task = moddle.create('bpmn:Task', {
name: 'foo'
});
eventBus.on('moddleCopy.canCopyProperties', HIGH_PRIORITY, function(context) {
var sourceElement = context.sourceElement;
if (is(sourceElement, 'bpmn:Task')) {
return false;
}
});
// when
var userTask = moddleCopy.copyElement(task, moddle.create('bpmn:UserTask'));
// then
expect(userTask.name).not.to.exist;
}));
it('should disallow copying property', inject(function(moddleCopy, eventBus, moddle) {
// given
var task = moddle.create('bpmn:Task', {
name: 'foo'
});
eventBus.on('moddleCopy.canCopyProperty', HIGH_PRIORITY, function(context) {
// verify provided properties
expect(context).to.have.property('parent');
expect(context).to.have.property('property');
expect(context).to.have.property('propertyName');
var propertyName = context.propertyName;
if (propertyName === 'name') {
return false;
}
});
// when
var userTask = moddleCopy.copyElement(task, moddle.create('bpmn:UserTask'));
// then
expect(userTask.name).not.to.exist;
}));
it('should disallow setting copied property', inject(function(moddleCopy, eventBus, moddle) {
// given
var task = moddle.create('bpmn:Task', {
name: 'foo'
});
eventBus.on('moddleCopy.canSetCopiedProperty', HIGH_PRIORITY, function(context) {
// verify provided properties
expect(context).to.have.property('parent');
expect(context).to.have.property('property');
expect(context).to.have.property('propertyName');
var property = context.property;
if (property === 'foo') {
return false;
}
});
// when
var userTask = moddleCopy.copyElement(task, moddle.create('bpmn:UserTask'));
// then
expect(userTask.name).not.to.exist;
}));
it('should copy primitive property', inject(function(moddleCopy, eventBus, moddle) {
// given
var task = moddle.create('bpmn:Task', {
name: 'foo'
});
eventBus.on('moddleCopy.canCopyProperty', HIGH_PRIORITY, function(context) {
var propertyName = context.propertyName;
if (propertyName === 'name') {
return 'bar';
}
});
// when
var userTask = moddleCopy.copyElement(task, moddle.create('bpmn:UserTask'));
// then
expect(userTask.name).to.equal('bar');
}));
describe('copy model element property', function() {
it('should copy and set parent', inject(function(moddleCopy, eventBus, moddle) {
// given
var startEvent = moddle.create('bpmn:StartEvent'),
messageEventDefinition = moddle.create('bpmn:MessageEventDefinition');
messageEventDefinition.$parent = startEvent;
startEvent.eventDefinitions = [ messageEventDefinition ];
eventBus.on('moddleCopy.canCopyProperty', HIGH_PRIORITY, function(context) {
var property = context.property;
if (is(property, 'bpmn:MessageEventDefinition')) {
return moddle.create('bpmn:MessageEventDefinition');
}
});
// when
var endEvent = moddleCopy.copyElement(startEvent, moddle.create('bpmn:EndEvent'));
// then
expect(endEvent.eventDefinitions).to.have.length(1);
expect(endEvent.eventDefinitions[0]).not.to.equal(messageEventDefinition);
expect(endEvent.eventDefinitions[0].$type).to.equal('bpmn:MessageEventDefinition');
}));
it('should clone', inject(function(moddleCopy, eventBus, moddle) {
// given
var task = moddle.create('bpmn:Task', {
name: 'foo'
});
eventBus.once('moddleCopy.canCopyProperty', HIGH_PRIORITY, function(context) {
var propertyName = context.propertyName;
var clone = context.clone;
expect(clone).to.be.true;
if (propertyName === 'name') {
return 'bar';
}
});
// when
var userTask = moddleCopy.copyElement(task, moddle.create('bpmn:UserTask'), null, true);
// then
expect(userTask.id).to.eql(task.id);
}));
});
it('should copy and NOT set parent', inject(function(canvas, moddleCopy, eventBus, moddle) {
// given
var definitions = getDefinitions(canvas.getRootElement()),
categoryValue = moddle.create('bpmn:CategoryValue'),
category = moddle.create('bpmn:Category'),
group = moddle.create('bpmn:Group'),
newCategoryValue,
newCategory,
newGroup;
categoryValue.$parent = category;
category.$parent = definitions;
category.categoryValue = [ categoryValue ];
definitions.rootElements.push(category);
group.categoryValueRef = categoryValue;
eventBus.on('moddleCopy.canCopyProperty', HIGH_PRIORITY, function(context) {
var propertyName = context.propertyName;
if (propertyName !== 'categoryValueRef') {
return;
}
newCategoryValue = moddle.create('bpmn:CategoryValue');
newCategory = moddle.create('bpmn:Category');
newCategoryValue.$parent = newCategory;
newCategory.$parent = definitions;
newCategory.categoryValue = [ newCategoryValue ];
definitions.rootElements.push(newCategory);
return newCategoryValue;
});
// when
newGroup = moddleCopy.copyElement(group, moddle.create('bpmn:Group'));
// then
expect(newGroup.categoryValueRef).to.exist;
expect(newGroup.categoryValueRef).not.to.equal(categoryValue);
expect(newGroup.categoryValueRef.$parent).to.equal(newCategory);
}));
describe('default events', function() {
describe('allowed references', function() {
it('should copy error reference', inject(function(moddle, moddleCopy) {
// given
var boundaryEvent = moddle.create('bpmn:BoundaryEvent'),
errorEventDefinition = moddle.create('bpmn:ErrorEventDefinition');
errorEventDefinition.$parent = boundaryEvent;
boundaryEvent.eventDefinitions = [ errorEventDefinition ];
var error = moddle.create('bpmn:Error');
errorEventDefinition.errorRef = error;
// when
var boundaryEventCopy = moddleCopy.copyElement(boundaryEvent, moddle.create('bpmn:BoundaryEvent'));
// then
expect(boundaryEventCopy.eventDefinitions).to.have.length(1);
expect(boundaryEventCopy.eventDefinitions[0]).not.to.equal(errorEventDefinition);
expect(boundaryEventCopy.eventDefinitions[0].$type).to.equal('bpmn:ErrorEventDefinition');
expect(boundaryEventCopy.eventDefinitions[0].errorRef).to.exist;
expect(boundaryEventCopy.eventDefinitions[0].errorRef).to.equal(error);
}));
it('should copy escalation reference', inject(function(moddle, moddleCopy) {
// given
var boundaryEvent = moddle.create('bpmn:BoundaryEvent'),
escalationEventDefinition = moddle.create('bpmn:EscalationEventDefinition');
escalationEventDefinition.$parent = boundaryEvent;
boundaryEvent.eventDefinitions = [ escalationEventDefinition ];
var error = moddle.create('bpmn:Escalation');
escalationEventDefinition.escalationRef = error;
// when
var boundaryEventCopy = moddleCopy.copyElement(boundaryEvent, moddle.create('bpmn:BoundaryEvent'));
// then
expect(boundaryEventCopy.eventDefinitions).to.have.length(1);
expect(boundaryEventCopy.eventDefinitions[0]).not.to.equal(escalationEventDefinition);
expect(boundaryEventCopy.eventDefinitions[0].$type).to.equal('bpmn:EscalationEventDefinition');
expect(boundaryEventCopy.eventDefinitions[0].escalationRef).to.exist;
expect(boundaryEventCopy.eventDefinitions[0].escalationRef).to.equal(error);
}));
it('should copy message reference (event)', inject(function(moddle, moddleCopy) {
// given
var boundaryEvent = moddle.create('bpmn:BoundaryEvent'),
messageEventDefinition = moddle.create('bpmn:MessageEventDefinition');
messageEventDefinition.$parent = boundaryEvent;
boundaryEvent.eventDefinitions = [ messageEventDefinition ];
var message = moddle.create('bpmn:Message');
messageEventDefinition.messageRef = message;
// when
var boundaryEventCopy = moddleCopy.copyElement(boundaryEvent, moddle.create('bpmn:BoundaryEvent'));
// then
expect(boundaryEventCopy.eventDefinitions).to.have.length(1);
expect(boundaryEventCopy.eventDefinitions[0]).not.to.equal(messageEventDefinition);
expect(boundaryEventCopy.eventDefinitions[0].$type).to.equal('bpmn:MessageEventDefinition');
expect(boundaryEventCopy.eventDefinitions[0].messageRef).to.exist;
expect(boundaryEventCopy.eventDefinitions[0].messageRef).to.equal(message);
}));
it('should copy message reference (receive task)', inject(function(moddle, moddleCopy) {
// given
var receiveTask = moddle.create('bpmn:ReceiveTask');
var message = moddle.create('bpmn:Message');
receiveTask.messageRef = message;
// when
var receiveTaskCopy = moddleCopy.copyElement(receiveTask, moddle.create('bpmn:ReceiveTask'));
// then
expect(receiveTaskCopy.messageRef).to.exist;
expect(receiveTaskCopy.messageRef).to.equal(message);
}));
it('should copy signal reference', inject(function(moddle, moddleCopy) {
// given
var boundaryEvent = moddle.create('bpmn:BoundaryEvent'),
signalEventDefinition = moddle.create('bpmn:SignalEventDefinition');
signalEventDefinition.$parent = boundaryEvent;
boundaryEvent.eventDefinitions = [ signalEventDefinition ];
var signal = moddle.create('bpmn:Signal');
signalEventDefinition.signalRef = signal;
// when
var boundaryEventCopy = moddleCopy.copyElement(boundaryEvent, moddle.create('bpmn:BoundaryEvent'));
// then
expect(boundaryEventCopy.eventDefinitions).to.have.length(1);
expect(boundaryEventCopy.eventDefinitions[0]).not.to.equal(signalEventDefinition);
expect(boundaryEventCopy.eventDefinitions[0].$type).to.equal('bpmn:SignalEventDefinition');
expect(boundaryEventCopy.eventDefinitions[0].signalRef).to.exist;
expect(boundaryEventCopy.eventDefinitions[0].signalRef).to.equal(signal);
}));
});
describe('disallowed properties', function() {
it('should NOT copy incoming and outgoing', inject(function(moddle, moddleCopy) {
// given
var incoming = moddle.create('bpmn:SequenceFlow'),
outgoing = moddle.create('bpmn:SequenceFlow'),
task = moddle.create('bpmn:Task', {
incoming: [ incoming ],
outgoing: [ outgoing ]
});
expect(task.get('incoming')).to.have.length(1);
expect(task.get('outgoing')).to.have.length(1);
// when
var taskCopy = moddleCopy.copyElement(task, moddle.create('bpmn:Task'));
// then
expect(taskCopy.get('incoming')).to.have.length(0);
expect(taskCopy.get('outgoing')).to.have.length(0);
}));
});
});
});
describe('custom', function() {
var customPackage = require('../../../fixtures/json/model/custom.json');
beforeEach(bootstrapModeler(basicXML, {
modules: testModules,
moddleExtensions: {
custom: customPackage
}
}));
it('should copy arrays of strings', inject(function(moddle, moddleCopy) {
// given
var paths = [ 'A', 'B', 'C' ];
var customElement = moddle.create('custom:CustomSendElement', {
paths: paths
});
// when
var newElement = moddleCopy.copyElement(
customElement,
moddle.create('custom:CustomSendElement')
);
// then
expect(newElement.paths).to.have.length(3);
expect(newElement.paths).to.eql(paths);
expectNoAttrs(newElement);
}));
});
});
// helpers //////////
function expectNoAttrs(element) {
expect(element.$attrs).to.be.empty;
}
function getDefinitions(rootElement) {
var businessObject = getBusinessObject(rootElement);
return businessObject.$parent;
}
================================================
FILE: test/spec/features/copy-paste/basic.bpmn
================================================
SequenceFlow_1
SequenceFlow_2
SequenceFlow_1
SequenceFlow_2
SequenceFlow_3
SequenceFlow_3
foo
foo
================================================
FILE: test/spec/features/copy-paste/collaboration-multiple.bpmn
================================================
Task_2
Task_1
Task_1
EndEvent_1
IntermediateThrowEvent_1
StartEvent_1
================================================
FILE: test/spec/features/copy-paste/collaboration.bpmn
================================================
StartEvent_1
EndEvent_1
Task_1
SequenceFlow_1
SequenceFlow_2
SequenceFlow_1
SequenceFlow_2
================================================
FILE: test/spec/features/copy-paste/collapsed-subprocess.bpmn
================================================
================================================
FILE: test/spec/features/copy-paste/complex.bpmn
================================================
sid-8E5D2B9D-6731-4FA7-BB27-C444A7236A69
sid-028EEE79-92D7-4C1B-B90D-905AA2697614
sid-7C1A234F-E066-438E-96E1-C0D7847288EA
sid-6EED0CFD-023A-4ACC-B944-42D90A617BDF
sid-CCECC1E8-CA9C-49BB-BEDF-75F173A11194
sid-F1FC14E4-8BBB-4647-9F9D-179663172496
sid-FF2BEA0D-55D5-4F2B-B7BA-195BC10CA9EB
sid-8910ED9B-DADC-4E11-9AD5-E12448B57ADF
sid-545B3227-D12A-43A8-B746-55E8C75F3A8A
sid-0FE7F936-F79B-4EB8-95F0-DC0AB97C682F
sid-05FFFE92-8A4C-43AB-8D9A-E14A1B0EE051
sid-28EA3990-9252-49F9-AD56-8FCDBFD7DA7D
sid-25203984-1D0A-494D-AD69-9140631D10FD
sid-6243D8FF-E57D-4D03-8234-40836D4E60D5
sid-72EBDBE9-3856-4AF7-9DDB-7C0F478E26D8
sid-57892D76-E413-4274-B8DE-FED72250C8A7
sid-6392228F-E287-40F3-9DD6-B91493F6B671
sid-EC21E3DA-12C5-4B74-83F7-530229F6C777
sid-A83B900A-A119-4FC4-A77F-09849C8660C9
sid-4A0A3787-3011-42F1-8CF7-16479922159E
sid-4A0A3787-3011-42F1-8CF7-16479922159E
sid-8F7A2A97-7C59-4B9E-AE03-625BB085C0E4
sid-028EEE79-92D7-4C1B-B90D-905AA2697614
sid-E8C87193-03FE-438D-A921-0BAB9FBD08D9
sid-8F7A2A97-7C59-4B9E-AE03-625BB085C0E4
sid-7E6EBD99-9B3A-44CE-972E-9CCDA7924AD8
sid-FD9AF1C4-E124-4C55-9B18-87804F8EC67E
sid-85F7873E-4458-4406-9D6E-1F4CA6268D55
sid-85F7873E-4458-4406-9D6E-1F4CA6268D55
sid-0D428B09-7C49-44AE-A257-7E810A541B0F
sid-F86867DE-BA70-47C1-8340-A6A5A3B645AA
sid-F86867DE-BA70-47C1-8340-A6A5A3B645AA
sid-DC611CCE-BA2C-459D-974D-4D09E2C390E6
sid-B9DF0BE4-4658-4908-8E4C-B528E8EA1FDD
sid-7BA05743-5D0C-4D1C-B193-22FE2A156E22
sid-DC611CCE-BA2C-459D-974D-4D09E2C390E6
sid-3C44D333-01F3-43B7-AE4F-F13DD6D05DAC
sid-B9DF0BE4-4658-4908-8E4C-B528E8EA1FDD
sid-F9A96365-936B-4461-8D51-C38EBA362A68
sid-F9A96365-936B-4461-8D51-C38EBA362A68
sid-9DE1A24F-5ED5-4095-B9C8-B7213895C7B7
sid-9DE1A24F-5ED5-4095-B9C8-B7213895C7B7
sid-950B6B3A-BEDA-49E3-A901-6733165E80C3
sid-950B6B3A-BEDA-49E3-A901-6733165E80C3
sid-3C44D333-01F3-43B7-AE4F-F13DD6D05DAC
sid-E54CB8B4-BED9-43AF-B03B-DBE60483A68A
sid-AEE9D356-E86D-4D7A-ABC5-AA6E76635A57
sid-C2A9F8A3-0E65-4BA2-A854-4B34EE2D2DFD
sid-C2A9F8A3-0E65-4BA2-A854-4B34EE2D2DFD
sid-7BA05743-5D0C-4D1C-B193-22FE2A156E22
sid-7E6EBD99-9B3A-44CE-972E-9CCDA7924AD8
sid-FD9AF1C4-E124-4C55-9B18-87804F8EC67E
sid-62177F1C-D8D7-488A-9F8A-E379831B4792
sid-A83B900A-A119-4FC4-A77F-09849C8660C9
sid-8BC0544C-1924-4422-B5BE-5CC1501312F4
sid-0D428B09-7C49-44AE-A257-7E810A541B0F
sid-62177F1C-D8D7-488A-9F8A-E379831B4792
sid-E54CB8B4-BED9-43AF-B03B-DBE60483A68A
sid-8BC0544C-1924-4422-B5BE-5CC1501312F4
sid-AEE9D356-E86D-4D7A-ABC5-AA6E76635A57
ID2
sid-A9859F1C-A85B-4F2F-B2DF-E3F4F7FA67FA
sid-B9B94A24-2819-461A-B5E8-61182BDA87DD
sid-86DC77E1-24A3-499E-89D0-499B4BBFF67A
sid-F0DA46E1-7E86-4236-9D49-290DD7B87C61
sid-BEC67F12-04E9-4AE2-B59E-FEB109CD3DF8
sid-303796CF-BA87-40A7-B2EC-9F6E10BD1060
sid-D3CA93A5-3BCC-4DB3-8075-EAC0C0A53991
sid-282D9841-8694-47FB-A058-32A5B47CFB1E
sid-597B2F0C-9759-421B-8F52-D8F26705F2BE
sid-6460C9E2-AD85-4EA6-8922-F9BAB25F1C5F
sid-F3E23BCC-F29B-444C-BF2B-C2AA7D9D0DEF
sid-770DE0CC-14BD-46C1-B93A-3C10BD8A3933
sid-E8C87193-03FE-438D-A921-0BAB9FBD08D9
Property_17padiq
sid-A9859F1C-A85B-4F2F-B2DF-E3F4F7FA67FA
sid-8D8BD39F-1B08-433F-8F93-A1FF7520BA8B
sid-770DE0CC-14BD-46C1-B93A-3C10BD8A3933
sid-155BB3AD-386C-4EF2-92C6-E2800D82A875
sid-3EB9F09F-9E44-4200-A845-09B78E5F6BA8
sid-F3E23BCC-F29B-444C-BF2B-C2AA7D9D0DEF
sid-3EB9F09F-9E44-4200-A845-09B78E5F6BA8
sid-BDC5800E-05A9-4D22-BA46-474DD7EBFF31
sid-8D8BD39F-1B08-433F-8F93-A1FF7520BA8B
Property_1qmn4q3
sid-F0DA46E1-7E86-4236-9D49-290DD7B87C61
sid-8D8BD39F-1B08-433F-8F93-A1FF7520BA8B
sid-155BB3AD-386C-4EF2-92C6-E2800D82A875
sid-BDC5800E-05A9-4D22-BA46-474DD7EBFF31
sid-F834923E-C5BB-44FF-9238-FE9B18B7ECD2
sid-F834923E-C5BB-44FF-9238-FE9B18B7ECD2
sid-EA96E73A-B3ED-48A6-B066-5B38CA75DBE6
sid-EA96E73A-B3ED-48A6-B066-5B38CA75DBE6
sid-1D3A1400-EBE2-4C67-B377-CC31EB9925E5
sid-1D3A1400-EBE2-4C67-B377-CC31EB9925E5
sid-54D77E49-FBFB-4B81-932A-B8F5E3BE4C6A
sid-54D77E49-FBFB-4B81-932A-B8F5E3BE4C6A
Frist = 1 Monate vor Besetzung der Stelle
ID1
ID13
sid-F5C79AC7-BB82-4CEC-A1E0-DB441A30CE08
sid-FB3673BF-5359-4ACE-B2A2-1C546E2D95C0
sid-2416E79A-ED80-4834-B7A9-A2F6F1B1F5D8
sid-DDA78FB8-B5BB-490F-8AB6-144A28AB646B
sid-8ED06068-AA04-428D-860D-8CA2A0483C2A
sid-AD5109AB-9014-4810-AB7B-3A4990FB44D1
sid-6B8D60D6-2BC0-4969-A16A-ABCD8586DEAD
sid-BA5E7744-B79B-47D2-8D85-7D38B00D52CF
sid-23B471A9-0A76-483C-AD0B-65092D0477BF
sid-CD80152E-8A60-4DF8-87F7-23BD0CDA3C58
sid-CD80152E-8A60-4DF8-87F7-23BD0CDA3C58
sid-FDB13333-B493-4477-9DE4-C7E4C522F495
sid-8006DF6D-4AC4-4BD4-8BE5-95CA3CCC182D
Property_0rxasu3
sid-1B290110-0336-46CF-92EC-C45D43FA9307
sid-3E4A80E2-14C5-4002-BFE9-15F53566593B
sid-3E4A80E2-14C5-4002-BFE9-15F53566593B
sid-908C781B-BB04-488F-BA6F-07FCF03BDD32
sid-E5370FBF-0192-43B6-8288-43F5D7BE5854
sid-908C781B-BB04-488F-BA6F-07FCF03BDD32
sid-50B9F1DF-70D3-48DF-A96D-482225E11A7D
sid-50B9F1DF-70D3-48DF-A96D-482225E11A7D
sid-E5370FBF-0192-43B6-8288-43F5D7BE5854
sid-FDB13333-B493-4477-9DE4-C7E4C522F495
sid-1B290110-0336-46CF-92EC-C45D43FA9307
sid-1E8A77DE-E3F1-4DFA-B20C-9EEBF0735E05
sid-1E8A77DE-E3F1-4DFA-B20C-9EEBF0735E05
sid-991275D8-E60A-440A-B2DD-094D2B0049A4
sid-19BF350B-2D34-4049-B17C-AE0265F407CB
sid-D56D4E10-864F-4B2B-898E-AA9641C98E63
sid-0C2D523B-E4EB-4776-AFA3-43B156AAE378
sid-2D215BCD-C98A-4B3A-B255-EDCE32FF2A97
sid-2D215BCD-C98A-4B3A-B255-EDCE32FF2A97
sid-0B7D8255-893E-404A-B0EF-CCB418B98B58
sid-0B7D8255-893E-404A-B0EF-CCB418B98B58
sid-73C9EBCD-9625-4469-B4AD-87B829C4BD8B
sid-73C9EBCD-9625-4469-B4AD-87B829C4BD8B
sid-699FBA56-8A57-43AC-ACC4-090A9B4AB26A
sid-1FFCF5B7-A6EE-4D63-AC49-5CD21F277383
sid-CC14630C-7FFB-4CD2-954A-1D52340F34B4
sid-8BC4F3B3-CA44-48C2-B3FA-137EAEC7D012
sid-F197542D-D274-45BE-95A5-7A3608BBE27A
sid-3820CBDB-C1E3-47EA-BCE1-10C9DBAEDB37
sid-308728F0-D1C5-4383-AA70-41249841A930
sid-4FA5730A-F51C-4CEE-98F0-631553512966
sid-7F1356BF-93F9-41C2-937C-E943B8818EB3
sid-47EBB822-715D-4193-8711-59063E3E4F48
sid-47EBB822-715D-4193-8711-59063E3E4F48
sid-B4BDF60C-F40F-4247-B52F-0EA0038A9039
sid-8006DF6D-4AC4-4BD4-8BE5-95CA3CCC182D
Property_15vitdv
sid-EF5AD02F-469C-45DB-AABA-29D5CDD54B58
sid-5C3AB4AB-5292-4EC3-9A0F-BAD26D56DBD4
sid-5C3AB4AB-5292-4EC3-9A0F-BAD26D56DBD4
sid-5C2E761C-74FF-405C-8F6E-416329D714BC
sid-E725BF0F-5B88-4D38-AE6A-7ABDA67CA7C7
sid-E725BF0F-5B88-4D38-AE6A-7ABDA67CA7C7
sid-5529AAF3-EC3E-409F-A020-8D0330A547E3
sid-5529AAF3-EC3E-409F-A020-8D0330A547E3
sid-5C2E761C-74FF-405C-8F6E-416329D714BC
sid-B4BDF60C-F40F-4247-B52F-0EA0038A9039
sid-F11A1E78-0DE1-46E5-9074-F665ED1985BE
sid-EF5AD02F-469C-45DB-AABA-29D5CDD54B58
sid-F11A1E78-0DE1-46E5-9074-F665ED1985BE
sid-B7711D66-52EB-4437-AA0C-5671CE83C6E6
sid-066B769F-EAEB-42E5-ACAB-341240A5F87D
sid-4F2CDAD6-92D6-470B-B82C-8D07E050591A
sid-943A343F-C038-49B9-8640-7BDABFB8E1BF
sid-F88DDF30-3F4D-4FA6-AA82-3B10300FFE98
sid-F88DDF30-3F4D-4FA6-AA82-3B10300FFE98
sid-4E1F9DDA-0BDF-4BB0-90F5-E7A85C259444
sid-4E1F9DDA-0BDF-4BB0-90F5-E7A85C259444
sid-6E0CD175-BAC0-4104-8555-C2473AD6956B
sid-6E0CD175-BAC0-4104-8555-C2473AD6956B
sid-155B51B2-12E4-4A0B-AFDF-DDBA2EE093D5
sid-3B833F36-ABCE-49A9-B268-445BAC9F758D
sid-A56AC510-264D-4291-B1CE-A035C2037437
sid-9A0A93AB-0A05-4D01-BF02-14A9C3189E84
sid-8CE3896D-0181-49CE-A1A9-C085262DA0FA
sid-42305309-4E98-47F0-80C4-690E2DB222C0
sid-4C1265A5-A535-48FB-99E9-68E0EC37C624
sid-53EE6F0D-43BE-4883-B018-3BB90743DC25
sid-91FF7684-71E6-497E-9938-0C9469B597EA
sid-286F337E-9361-4B4D-AF78-7E403CA80DA1
sid-DC5EFA1A-9841-4010-A16B-D771AA2B57C3
sid-8700D448-747C-4559-9A57-BFE8AAD639D4
sid-DB5144E8-CEDC-4333-BE75-A63907FDC5F0
sid-3906E383-5898-47F6-84FE-CBA12360BCF9
sid-11A2840A-596D-4937-BB37-0D5952E03535
sid-3906E383-5898-47F6-84FE-CBA12360BCF9
FOO
sid-D3213E0A-906E-4276-8DAF-208E4F416D51
sid-CBB13D0A-5D67-4908-9D56-4E1A1E2D0E71
sid-11A2840A-596D-4937-BB37-0D5952E03535
sid-D3213E0A-906E-4276-8DAF-208E4F416D51
sid-8E8DC42E-9AD4-4D6F-AEFC-C6FE6E91B6D3
sid-8E8DC42E-9AD4-4D6F-AEFC-C6FE6E91B6D3
sid-D3E0C8B2-1DEC-4283-9FB3-977DB4382650
sid-D3E0C8B2-1DEC-4283-9FB3-977DB4382650
sid-1CA535D5-1E51-41B7-A555-3E8BB651FE97
sid-CBB13D0A-5D67-4908-9D56-4E1A1E2D0E71
sid-1CA535D5-1E51-41B7-A555-3E8BB651FE97
sid-47FE04AD-8529-45E5-87E6-F7992404494A
sid-47FE04AD-8529-45E5-87E6-F7992404494A
sid-778CB5F7-7B50-4086-AEAF-A84CA5D34A8F
sid-778CB5F7-7B50-4086-AEAF-A84CA5D34A8F
sid-CB506E96-0C3C-4B65-9290-18EA192E62FE
sid-67782ED7-78C0-47F3-9EAC-46F9F194D843
sid-CB506E96-0C3C-4B65-9290-18EA192E62FE
sid-7363B1EE-93CD-49D4-966B-8F1368A01010
sid-7363B1EE-93CD-49D4-966B-8F1368A01010
sid-67782ED7-78C0-47F3-9EAC-46F9F194D843
sid-ADF333C6-9197-4556-A2C4-19BBC7122609
sid-ADF333C6-9197-4556-A2C4-19BBC7122609
ID7
ID5
ID6
ID1: Was passiert wenn sich kein Bewerber gemeldet hat? --> Anpassen der Stellenausschreibung, löschen dieser?
ID2: Die Bewerbung soll auch archiviert werden, jedoch mit Status "bei Eingang abgelehnt"
ID3: Die Informationen wann welcher Bewerber eingeladen wurde, soll bitte im Tool erfasst werden.
ID4: Müssen hier die Bewerbungs-unterlagen zurückgesendet werden?
ID5: Müssen hier die Bewerbungsunterlagen zurückgesendet werden?
ID6: kann der Bewerber auch "geparkt" werden, wenn weitere Gespräche folgen? wenn ja, wie soll das geschehen?
ID7: Was passiert wenn der Teilnehmer nicht erscheint?
ID8: Müssen hier die Bewerbungsunterlagen zurückgesendet werden?
ID9: Wie laufen jetzt alle anderen Bewerbungsgespräche? Werden die neu gestartet?
ID10: Muss jetzt die Stelle offline genommen werden?
ID11: Wie funktioniert das genau? Prozesskopplung zu Management Bewerbungsgespräche?
ID12: Soll IT-unterstützt laufen, gemäß vordefiniertem Template
ID13: Vorgabe wie das Filtern von statten gehen soll?! --> Business Rules
Annahmen:
- alle Bewerbungen gehen über Personal ein.
- unvollständige Bewerbungen werden nicht verfolgt bzw. nach einer Frist verworfen
- Es wurden nur Bewerber eingeladen, da aus Personalsicht inhaltlich geprüft sind. (z.B. müssen die Bewerbungen vollständig sein!)
Fehlt noch:
- Teilnehmer kann rückmelden, dass er Bewerbung zurückzieht
- Teilnehmer kann rückmelden, dass er Bewerbungsgespräch nicht wahrnehmen kann (komplette Absage oder neuen Termin vereinbaren)
- Verwendung von Daten ist nicht konsistent im Modell eingezeichnet
- Klärung der offenen ToDos direkt im Diagramm
sid-36E152C5-1864-4D96-9F9B-27133FD47EFE
sid-E91A2C66-8518-4301-8912-DA783975DD45
sid-B1C30549-F180-4515-9926-F2036892B4C1
sid-369E18F1-20E3-4E09-A8B7-1FF568E57F23
sid-369E18F1-20E3-4E09-A8B7-1FF568E57F23
sid-D654E8D0-0A05-48F1-B102-B547E61DDDB6
sid-97D1C665-6495-46A4-BA35-8961F07A076F
sid-258E87AC-23E8-4C39-92C8-5AE7DE19992A
sid-F52F82C6-C6C5-4E39-A2A9-7F4F780748A5
sid-48AAE1F8-75D9-4031-ACAD-49BD8A16E9C8
sid-373FAB4D-A45B-45EC-9F85-B1076ABBFBA6
sid-FE1C5021-4268-4AE6-8FBD-9DF197259520
sid-9B1BDCA4-28B7-426E-A13C-66409FAC588B
sid-4D49AA03-7D9E-4086-A0D9-43E7C741DCD3
sid-9E3DEB10-BFD8-4D7D-80EC-F98CD2E65DA4
sid-F52F82C6-C6C5-4E39-A2A9-7F4F780748A5
sid-9E3DEB10-BFD8-4D7D-80EC-F98CD2E65DA4
sid-9FB7EAEB-5A8B-4D90-B3A7-6B52FCBD5D22
sid-4D49AA03-7D9E-4086-A0D9-43E7C741DCD3
sid-04E0977B-6883-4B93-AD20-5A2F8B496DE6
sid-9FB7EAEB-5A8B-4D90-B3A7-6B52FCBD5D22
sid-97D1C665-6495-46A4-BA35-8961F07A076F
sid-7E2E4777-EC3D-4484-8606-A1ACB6FE84BE
sid-9B1BDCA4-28B7-426E-A13C-66409FAC588B
sid-5F689961-FD7E-4383-8ECE-B6946007D211
sid-04E0977B-6883-4B93-AD20-5A2F8B496DE6
sid-4FA0508C-2BC1-4ABB-8F75-ED27215EE73D
sid-7E2E4777-EC3D-4484-8606-A1ACB6FE84BE
sid-373FAB4D-A45B-45EC-9F85-B1076ABBFBA6
sid-C642640E-F524-40F0-BD91-41961D14ED31
sid-5F689961-FD7E-4383-8ECE-B6946007D211
sid-258E87AC-23E8-4C39-92C8-5AE7DE19992A
sid-B8E5BEAC-DBF0-45D3-A8A5-1D0D1EF0E19B
sid-C642640E-F524-40F0-BD91-41961D14ED31
sid-7AD16339-3A08-4841-96D9-3164E76DCF8F
sid-4FA0508C-2BC1-4ABB-8F75-ED27215EE73D
sid-97FBBC72-9B16-470C-BAF3-445654369DF9
sid-7AD16339-3A08-4841-96D9-3164E76DCF8F
sid-1203CB8F-6985-4231-B352-DE313ECA48CE
sid-B8E5BEAC-DBF0-45D3-A8A5-1D0D1EF0E19B
sid-FE1C5021-4268-4AE6-8FBD-9DF197259520
sid-97FBBC72-9B16-470C-BAF3-445654369DF9
sid-1203CB8F-6985-4231-B352-DE313ECA48CE
sid-90EB4714-6D5C-48A0-87BB-BE2024FE22BD
sid-8BB76718-C32D-4E09-B80B-7DC168E99147
sid-48AAE1F8-75D9-4031-ACAD-49BD8A16E9C8
sid-90EB4714-6D5C-48A0-87BB-BE2024FE22BD
sid-8BB76718-C32D-4E09-B80B-7DC168E99147
ID4
ID3
sid-D654E8D0-0A05-48F1-B102-B547E61DDDB6
sid-B104C31F-A70F-4206-AF8E-442C5C2EEE49
sid-1F104A2B-C7CC-49E1-87F1-8391D31274BE
sid-AB73793C-D47A-4738-B34F-A82C6219A92C
sid-9838543B-F6B3-4432-A9B3-8B790A762147
sid-EA77B0E4-4512-4A05-B100-88605F5B7995
sid-2E9539B9-F0F1-4AEE-ADDB-4C8AD7A3920C
sid-4D91885E-63D7-460F-ACAB-3B1300D396FB
sid-C5457771-C93B-44FD-8D70-05849F25C775
sid-7C6C6457-0FF6-4074-BC87-D5653F7F8037
sid-C0A39E1E-6BA6-4A7C-98C4-597CEF56D803
sid-691FDEB6-F626-4AB1-8E90-3653C83C04EA
sid-DD0BC4E1-4AA3-4835-A477-373EA263A593
sid-D16273A3-B9E4-4D02-8072-3868DC29A662
sid-C01A5EDF-FDDA-4675-86DD-EC939CA503EA
sid-5C56D37A-6C15-43CB-8253-E56329A0F15B
sid-D453B3D6-0EA6-4607-A413-C6332ABE9F32
sid-07626FBD-C7FA-466D-AC08-F579B7A9C2EA
sid-17FF4D83-66C9-45AC-8B63-3A4BC45B94B3
sid-D833F570-90A1-46AB-B968-16751237C003
sid-D833F570-90A1-46AB-B968-16751237C003
sid-A2B3A2C7-61FA-40F5-9AF0-27FF0B6DE47D
sid-A2B3A2C7-61FA-40F5-9AF0-27FF0B6DE47D
sid-6AD87C60-225D-4BAB-8EAE-3AB8D0C31754
sid-B1598D75-E3CE-4CC5-8380-8FB570208B3B
sid-A2FA1E3C-8920-4F38-A2B1-BB75D9E33E85
sid-B1598D75-E3CE-4CC5-8380-8FB570208B3B
sid-8110675A-7E69-4B6B-95B9-9DE5DEF4BF32
sid-E1876DA2-53A4-4F8B-8392-20655993C733
sid-63119748-84AD-4A9B-8CDE-45B930B374B7
sid-63119748-84AD-4A9B-8CDE-45B930B374B7
sid-A2FA1E3C-8920-4F38-A2B1-BB75D9E33E85
sid-10D4CACF-4CA9-448C-93D1-BD9089C22BDE
sid-10D4CACF-4CA9-448C-93D1-BD9089C22BDE
sid-BD42B065-FCDE-4B2A-9107-3602645F43B1
sid-BD42B065-FCDE-4B2A-9107-3602645F43B1
sid-17B8A293-C347-4830-BFA1-E4941E9B120F
sid-6AD87C60-225D-4BAB-8EAE-3AB8D0C31754
sid-17B8A293-C347-4830-BFA1-E4941E9B120F
sid-73D82202-2D46-4210-9113-A2BE24C342C8
sid-17FF4D83-66C9-45AC-8B63-3A4BC45B94B3
sid-73D82202-2D46-4210-9113-A2BE24C342C8
sid-8110675A-7E69-4B6B-95B9-9DE5DEF4BF32
sid-E1876DA2-53A4-4F8B-8392-20655993C733
sid-FA4E05F6-ADB1-4F93-A0DA-3AEAEFB6D148
sid-E7730210-76BB-486F-BAB3-CA3994DF6AED
sid-FA4E05F6-ADB1-4F93-A0DA-3AEAEFB6D148
sid-713C88F2-5563-41E9-9351-F1FEEEBA72DA
sid-713C88F2-5563-41E9-9351-F1FEEEBA72DA
sid-E7730210-76BB-486F-BAB3-CA3994DF6AED
sid-2365FF07-4092-4B79-976A-AD192FE4E4E9
sid-2365FF07-4092-4B79-976A-AD192FE4E4E9
ID12
ID8,9
ID11
ID10
================================================
FILE: test/spec/features/copy-paste/copy-properties.bpmn
================================================
hello world
foo
bar
10
SequenceFlow_1
SequenceFlow_2
DataStoreReference_1
Property_0j0o7pl
DataObjectReference_1
Property_0j0o7pl
DataStoreReference_2
DataObjectReference_2
SequenceFlow_1
SequenceFlow_2
================================================
FILE: test/spec/features/copy-paste/data-associations.bpmn
================================================
DataStoreReference_1
DataStoreReference_2
Property_0l7g57i
================================================
FILE: test/spec/features/copy-paste/event-based-gateway.bpmn
================================================
SequenceFlow_4
SequenceFlow_4
================================================
FILE: test/spec/features/copy-paste/nested-subprocess-annotations.bpmn
================================================
Annotation sample
================================================
FILE: test/spec/features/copy-paste/properties.bpmn
================================================
================================================
FILE: test/spec/features/distribute-elements/BpmnDistributeElementsSpec.js
================================================
import { forEach } from 'min-dash';
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import bpmnDistributeElements from 'lib/features/distribute-elements';
import modelingModule from 'lib/features/modeling';
import coreModule from 'lib/core';
import { is } from 'lib/util/ModelUtil';
function last(arr) {
return arr[arr.length - 1];
}
describe('features/distribute-elements', function() {
var testModules = [ bpmnDistributeElements, modelingModule, coreModule ];
describe('basics', function() {
var basicXML = require('../../../fixtures/bpmn/distribute-elements.bpmn');
beforeEach(bootstrapModeler(basicXML, { modules: testModules }));
var elements;
beforeEach(inject(function(elementRegistry, canvas) {
elements = elementRegistry.filter(function(element) {
return element.parent && !is(element, 'bpmn:Participant');
});
}));
it('should align horizontally', inject(function(distributeElements) {
// when
var rangeGroups = distributeElements.trigger(elements, 'horizontal'),
margin = rangeGroups[1].range.min - rangeGroups[0].range.max;
// then
expect(rangeGroups).to.have.length(9);
expect(margin).to.equal(83);
expect(rangeGroups[0].range).to.eql({
min: 132, max: 158
});
expect(last(rangeGroups).range).to.eql({
min: 1195, max: 1221
});
}));
it('should align vertically', inject(function(distributeElements) {
// when
var rangeGroups = distributeElements.trigger(elements, 'vertical'),
margin = rangeGroups[1].range.min - rangeGroups[0].range.max;
// then
expect(rangeGroups).to.have.length(4);
expect(margin).to.equal(17);
expect(rangeGroups[0].range).to.eql({
min: -42, max: 38
});
expect(last(rangeGroups).range).to.eql({
min: 373, max: 413
});
}));
});
describe('filtering elements', function() {
describe('process', function() {
var xml = require('../../../fixtures/bpmn/distribute-elements-filtering.bpmn'),
elements;
beforeEach(bootstrapModeler(xml, { modules: testModules }));
beforeEach(inject(function(elementRegistry) {
elements = elementRegistry.filter(function(element) {
return element.parent;
});
}));
it('should not distribute boundary events', inject(function(distributeElements, elementRegistry) {
// given
var boundaryEvent = elementRegistry.get('BoundaryEvent_1');
// when
var rangeGroups = distributeElements.trigger(elements, 'horizontal');
// then
expect(rangeGroups).to.have.length(3);
forEach(rangeGroups, function(rangeGroup) {
expect(rangeGroup.elements).not.to.include(boundaryEvent);
});
}));
it('should not distribute sub process children', inject(
function(distributeElements, elementRegistry) {
// given
var childElement = elementRegistry.get('SubProcessChild');
// when
var rangeGroups = distributeElements.trigger(elements, 'horizontal');
// then
expect(rangeGroups).to.have.length(3);
forEach(rangeGroups, function(rangeGroup) {
expect(rangeGroup.elements).not.to.include(childElement);
});
})
);
});
describe('collaboration', function() {
var xml = require('../../../fixtures/bpmn/distribute-elements-filtering.collaboration.bpmn'),
elements;
beforeEach(bootstrapModeler(xml, { modules: testModules }));
beforeEach(inject(function(elementRegistry) {
elements = elementRegistry.filter(function(element) {
return element.parent;
});
}));
it('should distribute participants', inject(function(distributeElements, elementRegistry) {
// given
var participants = elementRegistry.filter(function(element) {
return is(element, 'bpmn:Participant');
});
// when
var rangeGroups = distributeElements.trigger(elements, 'vertical');
// then
expect(rangeGroups).to.have.length(3);
var distributedElements = [];
forEach(rangeGroups, function(rangeGroup) {
distributedElements = distributedElements.concat(rangeGroup.elements);
});
expect(distributedElements).to.have.length(3);
expect(distributedElements).to.have.members(participants);
}));
});
});
});
================================================
FILE: test/spec/features/distribute-elements/DistributeElementsMenuProviderSpec.js
================================================
import {
bootstrapModeler,
getBpmnJS,
inject
} from 'test/TestHelper';
import {
query as domQuery
} from 'min-dom';
import {
forEach
} from 'min-dash';
import distributeElementsModule from 'lib/features/distribute-elements';
import modelingModule from 'lib/features/modeling';
import coreModule from 'lib/core';
describe('features/distribute-elements - popup menu', function() {
var testModules = [ distributeElementsModule, modelingModule, coreModule ];
var basicXML = require('../../../fixtures/bpmn/distribute-elements.bpmn');
beforeEach(bootstrapModeler(basicXML, { modules: testModules }));
it('should provide distribution buttons', inject(function(elementRegistry, popupMenu) {
// given
var elements = [
elementRegistry.get('ExclusiveGateway_10cec0a'),
elementRegistry.get('Task_08pns8h'),
elementRegistry.get('Task_0511uak'),
elementRegistry.get('EndEvent_0c9irey')
];
// when
popupMenu.open(elements, 'align-elements', {
x: 0,
y: 0
});
// then
forEach([
'horizontal',
'vertical'
], function(distribution) {
expect(getEntry('distribute-elements-' + distribution)).to.exist;
});
}));
forEach([
'horizontal',
'vertical'
], function(distribution) {
it('should close popup menu when button is clicked', inject(
function(elementRegistry, popupMenu) {
// given
var elements = [
elementRegistry.get('ExclusiveGateway_10cec0a'),
elementRegistry.get('Task_08pns8h'),
elementRegistry.get('Task_0511uak'),
elementRegistry.get('EndEvent_0c9irey')
];
popupMenu.open(elements, 'align-elements', {
x: 0,
y: 0
});
var entry = getEntry('distribute-elements-' + distribution);
// when
entry.click();
// then
expect(popupMenu.isOpen()).to.be.false;
})
);
});
});
// helper //////////////////////////////////////////////////////////////////////
function getEntry(actionName) {
return padEntry(getBpmnJS().invoke(function(popupMenu) {
return popupMenu._current.container;
}), actionName);
}
function padEntry(element, name) {
return domQuery('[data-id="' + name + '"]', element);
}
================================================
FILE: test/spec/features/drilldown/DrilldownIntegrationSpec.js
================================================
import {
inject
} from 'test/TestHelper';
import coreModule from 'lib/core';
import modelingModule from 'lib/features/modeling';
import DrilldownModule from 'lib/features/drilldown';
import { bootstrapModeler, getBpmnJS } from '../../../helper';
describe('features - drilldown', function() {
var testModules = [
coreModule,
modelingModule,
DrilldownModule
];
var multiLayerXML = require('./nested-subprocesses.bpmn');
beforeEach(bootstrapModeler(multiLayerXML, { modules: testModules }));
describe('navigation - collaboration', function() {
var process, participant;
beforeEach(inject(function(canvas, elementFactory) {
process = canvas.getRootElement();
participant = elementFactory.createParticipantShape({ x: 100, y: 100 });
}));
it('should not reset scroll on create collaboration',
inject(function(canvas, modeling) {
// given
canvas.scroll({ dx: 500, dy: 500 });
canvas.zoom(0.5);
var zoomedAndScrolledViewbox = canvas.viewbox();
// when
modeling.createShape(participant, { x: 0, y: 0 }, process);
// then
expectViewbox(zoomedAndScrolledViewbox);
})
);
it('should not reset scroll on create collaboration - undo',
inject(function(canvas, modeling, commandStack) {
// given
canvas.scroll({ dx: 500, dy: 500 });
canvas.zoom(0.5);
var zoomedAndScrolledViewbox = canvas.viewbox();
// when
modeling.createShape(participant, { x: 0, y: 0 }, process);
commandStack.undo();
// then
expectViewbox(zoomedAndScrolledViewbox);
})
);
it('should not reset scroll on create collaboration - redo',
inject(function(canvas, modeling, commandStack) {
// given
canvas.scroll({ dx: 500, dy: 500 });
canvas.zoom(0.5);
var zoomedAndScrolledViewbox = canvas.viewbox();
// when
modeling.createShape(participant, { x: 400, y: 225 }, process);
commandStack.undo();
commandStack.redo();
// then
expectViewbox(zoomedAndScrolledViewbox);
})
);
it('should remember scroll and zoom after switching planes', inject(function(canvas, modeling) {
// given
canvas.scroll({ dx: 500, dy: 500 });
canvas.zoom(0.5);
var zoomedAndScrolledViewbox = canvas.viewbox();
modeling.createShape(participant, { x: 400, y: 225 }, process);
var collaboration = canvas.getRootElement();
// when
canvas.setRootElement(canvas.findRoot('collapsedProcess_plane'));
canvas.setRootElement(collaboration);
// then
var newViewbox = canvas.viewbox();
expect(newViewbox.x).to.eql(zoomedAndScrolledViewbox.x);
expect(newViewbox.y).to.eql(zoomedAndScrolledViewbox.y);
expect(newViewbox.scale).to.eql(zoomedAndScrolledViewbox.scale);
}));
});
describe('breadcrumbs - name changes', function() {
it('should update on plane name change',
inject(function(canvas, modeling) {
// given
canvas.setRootElement(canvas.findRoot('collapsedProcess_2_plane'));
// when
modeling.updateProperties(canvas.getRootElement(), { name: 'new name' });
// then
expectBreadcrumbs([
'Root',
'Collapsed Process',
'Expanded Process',
'new name'
]);
})
);
it('should update on element name change',
inject(function(canvas, elementRegistry, modeling) {
// given
canvas.setRootElement(canvas.findRoot('collapsedProcess_2_plane'));
var shape = elementRegistry.get('collapsedProcess_2');
// when
modeling.updateProperties(shape, { name: 'new name' });
// then
expectBreadcrumbs([
'Root',
'Collapsed Process',
'Expanded Process',
'new name'
]);
})
);
it('should update on process name change',
inject(function(canvas, elementRegistry, modeling) {
// given
canvas.setRootElement(canvas.findRoot('collapsedProcess_2_plane'));
var shape = elementRegistry.get('rootProcess');
// when
modeling.updateProperties(shape, { name: 'new name' });
// then
expectBreadcrumbs([
'new name',
'Collapsed Process',
'Expanded Process',
'Collapsed Process 2'
]);
})
);
});
});
// helpers //////////
function expectViewbox(expectedViewbox) {
return getBpmnJS().invoke(function(canvas) {
var viewbox = canvas.viewbox();
expect(viewbox.x).to.eql(expectedViewbox.x);
expect(viewbox.y).to.eql(expectedViewbox.y);
expect(viewbox.scale).to.eql(expectedViewbox.scale);
});
}
function getBreadcrumbs() {
return getBpmnJS().invoke(function(canvas) {
return canvas.getContainer().querySelector('.bjs-breadcrumbs');
});
}
function expectBreadcrumbs(expected) {
var breadcrumbs = getBreadcrumbs();
var crumbs = Array.from(breadcrumbs.children).map(function(element) {
return element.innerText;
});
expect(crumbs).to.eql(expected);
}
================================================
FILE: test/spec/features/drilldown/DrilldownOverlayBehaviorSpec.bpmn
================================================
================================================
FILE: test/spec/features/drilldown/DrilldownOverlaysBehaviorSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import coreModule from 'lib/core';
import modelingModule from 'lib/features/modeling';
import replaceModule from 'lib/features/replace';
import drilldownModule from 'lib/features/drilldown';
import { classes } from 'min-dom';
describe('features/modeling/behavior - subprocess planes', function() {
var diagramXML = require('./DrilldownOverlayBehaviorSpec.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: [
coreModule,
modelingModule,
replaceModule,
drilldownModule
]
}));
describe('create new drilldowns', function() {
it('should create drilldown for new process',
inject(function(elementFactory, modeling, canvas, overlays) {
// given
var subProcess = elementFactory.createShape({
type: 'bpmn:SubProcess',
isExpanded: false
});
// when
modeling.createShape(subProcess, { x: 300, y: 300 }, canvas.getRootElement());
// then
var elementOverlays = overlays.get({ element: subProcess });
expect(elementOverlays).to.not.be.empty;
})
);
it('should not create drilldown for expanded subprocess',
inject(function(elementFactory, modeling, canvas, overlays) {
// given
var subProcess = elementFactory.createShape({
type: 'bpmn:SubProcess',
isExpanded: true
});
// when
modeling.createShape(subProcess, { x: 300, y: 300 }, canvas.getRootElement());
// then
var elementOverlays = overlays.get({ element: subProcess });
expect(elementOverlays).to.be.empty;
})
);
it('should undo',
inject(function(elementFactory, modeling, commandStack, canvas, overlays) {
// given
var subProcess = elementFactory.createShape({
type: 'bpmn:SubProcess',
isExpanded: false
});
modeling.createShape(subProcess, { x: 300, y: 300 }, canvas.getRootElement());
// when
commandStack.undo();
// then
var elementOverlays = overlays.get({ element: subProcess });
expect(elementOverlays).to.be.empty;
})
);
it('should redo',
inject(function(elementFactory, modeling, commandStack, canvas, overlays) {
// given
var subProcess = elementFactory.createShape({
type: 'bpmn:SubProcess',
isExpanded: false
});
modeling.createShape(subProcess, { x: 300, y: 300 }, canvas.getRootElement());
// when
commandStack.undo();
commandStack.redo();
// then
var elementOverlays = overlays.get({ element: subProcess });
expect(elementOverlays).to.not.be.empty;
})
);
it('should recreate drilldown on undo delete',
inject(function(elementRegistry, modeling, commandStack, overlays) {
// given
var subProcess = elementRegistry.get('Subprocess_with_content');
modeling.removeShape(subProcess);
// when
commandStack.undo();
// then
var elementOverlays = overlays.get({ element: subProcess });
expect(elementOverlays).to.not.be.empty;
})
);
});
describe('overlay visibility', function() {
describe('empty subprocess', function() {
it('should hide drilldown', inject(function(elementRegistry, overlays) {
// given
var subProcess = elementRegistry.get('Subprocess_empty');
// then
var overlay = overlays.get({ element: subProcess })[0];
expect(classes(overlay.html).contains('bjs-drilldown-empty')).to.be.true;
}));
it('should show when content is added',
inject(function(elementRegistry, overlays, elementFactory, modeling, canvas) {
// given
var subProcess = elementRegistry.get('Subprocess_empty');
var task = elementFactory.createShape({ type: 'bpmn:Task' });
var planeRoot = canvas.findRoot('Subprocess_empty_plane');
// when
modeling.createShape(task, { x: 300, y: 300 }, planeRoot);
// then
var overlay = overlays.get({ element: subProcess })[0];
expect(classes(overlay.html).contains('bjs-drilldown-empty')).to.be.false;
})
);
it('should undo',
inject(function(elementRegistry, overlays, elementFactory,
modeling, canvas, commandStack) {
// given
var subProcess = elementRegistry.get('Subprocess_empty');
var task = elementFactory.createShape({ type: 'bpmn:Task' });
var planeRoot = canvas.findRoot('Subprocess_empty_plane');
modeling.createShape(task, { x: 300, y: 300 }, planeRoot);
// when
commandStack.undo();
// then
var overlay = overlays.get({ element: subProcess })[0];
expect(classes(overlay.html).contains('bjs-drilldown-empty')).to.be.true;
})
);
it('should redo',
inject(function(elementRegistry, overlays, elementFactory,
modeling, canvas, commandStack) {
// given
var subProcess = elementRegistry.get('Subprocess_empty');
var task = elementFactory.createShape({ type: 'bpmn:Task' });
var planeRoot = canvas.findRoot('Subprocess_empty_plane');
modeling.createShape(task, { x: 300, y: 300 }, planeRoot);
// when
commandStack.undo();
commandStack.redo();
// then
var overlay = overlays.get({ element: subProcess })[0];
expect(classes(overlay.html).contains('bjs-drilldown-empty')).to.be.false;
})
);
});
describe('subprocess with content', function() {
it('should show drilldown', inject(function(elementRegistry, overlays) {
// given
var subProcess = elementRegistry.get('Subprocess_with_content');
// then
var overlay = overlays.get({ element: subProcess })[0];
expect(classes(overlay.html).contains('bjs-drilldown-empty')).to.be.false;
}));
it('should hide when content is removed',
inject(function(elementRegistry, overlays, modeling) {
// given
var subProcess = elementRegistry.get('Subprocess_with_content');
var startEvent = elementRegistry.get('StartEvent_embedded');
// when
modeling.removeShape(startEvent);
// then
var overlay = overlays.get({ element: subProcess })[0];
expect(classes(overlay.html).contains('bjs-drilldown-empty')).to.be.true;
})
);
it('should undo',
inject(function(elementRegistry, overlays, modeling, commandStack) {
// given
var subProcess = elementRegistry.get('Subprocess_with_content');
var startEvent = elementRegistry.get('StartEvent_embedded');
modeling.removeShape(startEvent);
// when
commandStack.undo();
// then
var overlay = overlays.get({ element: subProcess })[0];
expect(classes(overlay.html).contains('bjs-drilldown-empty')).to.be.false;
})
);
it('should redo',
inject(function(elementRegistry, overlays, modeling, commandStack) {
// given
var subProcess = elementRegistry.get('Subprocess_with_content');
var startEvent = elementRegistry.get('StartEvent_embedded');
modeling.removeShape(startEvent);
// when
commandStack.undo();
commandStack.redo();
// then
var overlay = overlays.get({ element: subProcess })[0];
expect(classes(overlay.html).contains('bjs-drilldown-empty')).to.be.true;
})
);
});
});
describe('expand/collapse', function() {
describe('collapse', function() {
it('should create new overlay on collapse',
inject(function(elementRegistry, modeling, canvas, overlays) {
// given
var subProcess = elementRegistry.get('Subprocess_expanded');
// when
modeling.toggleCollapse(subProcess);
// then
var elementOverlays = overlays.get({ element: subProcess });
expect(elementOverlays).to.not.be.empty;
}));
it('should undo',
inject(function(elementRegistry, modeling, overlays, commandStack) {
// given
var subProcess = elementRegistry.get('Subprocess_expanded');
modeling.toggleCollapse(subProcess);
// when
commandStack.undo();
// then
var elementOverlays = overlays.get({ element: subProcess });
expect(elementOverlays).to.be.empty;
}));
it('should redo',
inject(function(elementRegistry, modeling, overlays, commandStack) {
// given
var subProcess = elementRegistry.get('Subprocess_expanded');
modeling.toggleCollapse(subProcess);
// when
commandStack.undo();
commandStack.redo();
// then
var elementOverlays = overlays.get({ element: subProcess });
expect(elementOverlays).to.not.be.empty;
}));
});
describe('expand', function() {
it('should remove overlay on expand',
inject(function(elementRegistry, modeling, overlays) {
// given
var subProcess = elementRegistry.get('Subprocess_with_content');
// when
modeling.toggleCollapse(subProcess);
// then
var elementOverlays = overlays.get({ element: subProcess });
expect(elementOverlays).to.be.empty;
}));
it('should undo',
inject(function(elementRegistry, modeling, overlays, commandStack) {
// given
var subProcess = elementRegistry.get('Subprocess_with_content');
modeling.toggleCollapse(subProcess);
// when
commandStack.undo();
// then
var elementOverlays = overlays.get({ element: subProcess });
expect(elementOverlays).to.not.empty;
}));
it('should redo',
inject(function(elementRegistry, modeling, overlays, commandStack) {
// given
var subProcess = elementRegistry.get('Subprocess_with_content');
modeling.toggleCollapse(subProcess);
// when
commandStack.undo();
commandStack.redo();
// then
var elementOverlays = overlays.get({ element: subProcess });
expect(elementOverlays).to.be.empty;
}));
});
});
it('should only show one overlay', inject(function(elementRegistry, overlays, modeling) {
// given
var filledSubProcess = elementRegistry.get('Subprocess_with_content');
var emptySubProcessPlane = elementRegistry.get('Subprocess_empty_plane');
// when
modeling.moveShape(filledSubProcess, { x: 0, y: 0 }, emptySubProcessPlane);
// then
var elementOverlays = overlays.get({ element: filledSubProcess });
expect(elementOverlays).to.have.lengthOf(1);
}));
});
================================================
FILE: test/spec/features/drilldown/DrilldownSpec.js
================================================
import { expectToBeAccessible } from '@bpmn-io/a11y';
import {
inject
} from 'test/TestHelper';
import coreModule from 'lib/core';
import drilldownModule from 'lib/features/drilldown';
import modelingModule from 'lib/features/modeling';
import { bootstrapModeler, getBpmnJS } from '../../../helper';
import { classes } from 'min-dom';
describe('features - drilldown', function() {
var testModules = [
coreModule,
modelingModule,
drilldownModule
];
var collaborationXML = require('./collaboration-subprocesses.bpmn');
var multiLayerXML = require('./nested-subprocesses.bpmn');
var legacyXML = require('./legacy-subprocesses.bpmn');
beforeEach(bootstrapModeler(multiLayerXML, { modules: testModules }));
describe('Overlays', function() {
it('should show overlay on Subprocess with content', inject(function(elementRegistry, overlays) {
// given
var collapsedProcess = elementRegistry.get('collapsedProcess');
var overlay = overlays.get({ element: collapsedProcess });
// then
expect(overlay).to.exist;
}));
it('should not show overlay on Subprocess without content', inject(function(elementRegistry, overlays) {
// given
var collapsedProcess = elementRegistry.get('collapsedProcess_withoutContent');
var overlay = overlays.get({ element: collapsedProcess });
// then
expect(overlay).to.not.exist;
}));
it('should switch plane on click', inject(function(elementRegistry, overlays, canvas) {
// given
var collapsedProcess = elementRegistry.get('collapsedProcess');
var overlay = overlays.get({ element: collapsedProcess })[0];
// when
overlay.html.click();
// then
var collapsedRoot = canvas.getRootElement();
expect(collapsedRoot.businessObject).to.equal(collapsedProcess.businessObject);
}));
});
describe('Breadcrumbs', function() {
it('should not show breadcrumbs in root view', inject(function(canvas) {
// given
var container = canvas.getContainer();
// then
expect(classes(container).contains('bjs-breadcrumbs-shown')).to.be.false;
}));
it('should show breadcrumbs in subprocess view', inject(function(canvas) {
// given
var container = canvas.getContainer();
// when
canvas.setRootElement(canvas.findRoot('collapsedProcess_plane'));
// then
expect(classes(container).contains('bjs-breadcrumbs-shown')).to.be.true;
}));
it('should show execution tree', inject(function(canvas) {
// when
canvas.setRootElement(canvas.findRoot('collapsedProcess_2_plane'));
// then
expectBreadcrumbs([
'Root',
'Collapsed Process',
'Expanded Process',
'Collapsed Process 2'
]);
}));
it('should switch to process plane on click', inject(function(canvas) {
// given
var subRoot = canvas.findRoot('collapsedProcess_plane');
var nestedRoot = canvas.findRoot('collapsedProcess_2_plane');
canvas.setRootElement(nestedRoot);
// when
clickBreadcrumb(1);
// then
expectBreadcrumbs([
'Root',
'Collapsed Process'
]);
expect(
canvas.getRootElement()
).to.equal(subRoot);
}));
it('should switch to root', inject(function(canvas) {
// given
var processRoot = canvas.findRoot('rootProcess');
var nestedRoot = canvas.findRoot('collapsedProcess_2_plane');
canvas.setRootElement(nestedRoot);
// when
clickBreadcrumb(0);
// then
expectBreadcrumbs([
'Root'
]);
expect(
canvas.getRootElement()
).to.equal(processRoot);
}));
it('should switch to containing process plane on embedded click', inject(function(canvas) {
// given
var subRoot = canvas.findRoot('collapsedProcess_plane');
var nestedRoot = canvas.findRoot('collapsedProcess_2_plane');
canvas.setRootElement(nestedRoot);
// when
clickBreadcrumb(2);
// then
expectBreadcrumbs([
'Root',
'Collapsed Process'
]);
expect(
canvas.getRootElement()
).to.equal(subRoot);
}));
});
describe('Navigation', function() {
it('should reset scroll and zoom', inject(function(canvas) {
// given
canvas.scroll({ dx: 500, dy: 500 });
canvas.zoom(0.5);
// when
canvas.setRootElement(canvas.findRoot('collapsedProcess_plane'));
// then
expectViewbox({
x: 0,
y: 0,
scale: 1
});
}));
it('should remember scroll and zoom', inject(function(canvas) {
// given
var rootRoot = canvas.getRootElement();
var planeRoot = canvas.findRoot('collapsedProcess_plane');
canvas.scroll({ dx: 500, dy: 500 });
canvas.zoom(0.5);
var rootViewbox = canvas.viewbox();
canvas.setRootElement(planeRoot);
canvas.scroll({ dx: 100, dy: 100 });
var planeViewbox = canvas.viewbox();
// when
canvas.setRootElement(rootRoot);
// then
expectViewbox(rootViewbox);
// but when
canvas.setRootElement(planeRoot);
// then
expectViewbox(planeViewbox);
}));
it('should not reset viewbox on root change', inject(function(canvas, modeling) {
// given
canvas.scroll({ dx: 500, dy: 500 });
canvas.zoom(0.5);
var zoomedAndScrolledViewbox = canvas.viewbox();
// when
modeling.makeCollaboration();
// then
expectViewbox(zoomedAndScrolledViewbox);
}));
// helpers //////////
function expectViewbox(expectedViewbox) {
return getBpmnJS().invoke(function(canvas) {
var viewbox = canvas.viewbox();
expect(viewbox.x).to.eql(expectedViewbox.x);
expect(viewbox.y).to.eql(expectedViewbox.y);
expect(viewbox.scale).to.eql(expectedViewbox.scale);
});
}
});
describe('Collaboration', function() {
beforeEach(bootstrapModeler(collaborationXML, { modules: testModules }));
describe('Overlays', function() {
it('should show overlay', inject(function(elementRegistry, overlays) {
// given
var collapsedProcess = elementRegistry.get('collapsedProcess');
var overlay = overlays.get({ element: collapsedProcess });
// then
expect(overlay).to.exist;
}));
it('should switch plane on click', inject(function(elementRegistry, overlays, canvas) {
// given
var collapsedProcess = elementRegistry.get('collapsedProcess');
var overlay = overlays.get({ element: collapsedProcess })[0];
// when
overlay.html.click();
// then
var collapsedRoot = canvas.getRootElement();
expect(collapsedRoot.businessObject).to.equal(collapsedProcess.businessObject);
}));
});
describe('Breadcrumbs', function() {
it('should not show breadcrumbs in root view', inject(function(canvas) {
// given
var container = canvas.getContainer();
// then
expect(classes(container).contains('bjs-breadcrumbs-shown')).to.be.false;
}));
it('should show breadcrumbs in subprocess view', inject(function(canvas) {
// given
var container = canvas.getContainer();
// when
canvas.setRootElement(canvas.findRoot('collapsedProcess_plane'));
// then
expect(classes(container).contains('bjs-breadcrumbs-shown')).to.be.true;
}));
it('should show execution tree', inject(function(canvas) {
// when
canvas.setRootElement(canvas.findRoot('collapsedProcess_2_plane'));
// then
expectBreadcrumbs([
'Root',
'Collapsed Process',
'Expanded Process',
'Collapsed Process 2'
]);
}));
it('should switch to process plane on click', inject(function(canvas) {
// given
var subRoot = canvas.findRoot('collapsedProcess_plane');
var nestedRoot = canvas.findRoot('collapsedProcess_2_plane');
canvas.setRootElement(nestedRoot);
// when
clickBreadcrumb(1);
// then
expectBreadcrumbs([
'Root',
'Collapsed Process'
]);
expect(
canvas.getRootElement()
).to.equal(subRoot);
}));
it('should switch to root', inject(function(canvas) {
// given
var collaboration = canvas.findRoot('Collaboration_0wnumpk');
var nestedRoot = canvas.findRoot('collapsedProcess_2_plane');
canvas.setRootElement(nestedRoot);
// when
clickBreadcrumb(0);
expect(
canvas.getRootElement()
).to.equal(collaboration);
}));
it('should switch to containing process plane on embedded click', inject(function(canvas) {
// given
var subRoot = canvas.findRoot('collapsedProcess_plane');
var nestedRoot = canvas.findRoot('collapsedProcess_2_plane');
canvas.setRootElement(nestedRoot);
// when
clickBreadcrumb(2);
// then
expectBreadcrumbs([
'Root',
'Collapsed Process'
]);
expect(
canvas.getRootElement()
).to.equal(subRoot);
}));
});
describe('Navigation', function() {
it('should reset scroll and zoom', inject(function(canvas) {
// given
canvas.scroll({ dx: 500, dy: 500 });
canvas.zoom(0.5);
// when
canvas.setRootElement(canvas.findRoot('collapsedProcess_plane'));
// then
expectViewbox({
x: 0,
y: 0,
scale: 1
});
}));
it('should remember scroll and zoom', inject(function(canvas) {
// given
var rootRoot = canvas.getRootElement();
var planeRoot = canvas.findRoot('collapsedProcess_plane');
canvas.scroll({ dx: 500, dy: 500 });
canvas.zoom(0.5);
var rootViewbox = canvas.viewbox();
canvas.setRootElement(planeRoot);
canvas.scroll({ dx: 100, dy: 100 });
var planeViewbox = canvas.viewbox();
// when
canvas.setRootElement(rootRoot);
// then
expectViewbox(rootViewbox);
// but when
canvas.setRootElement(planeRoot);
// then
expectViewbox(planeViewbox);
}));
it('should not reset viewbox on root change', inject(function(canvas, modeling) {
// given
canvas.scroll({ dx: 500, dy: 500 });
canvas.zoom(0.5);
var zoomedAndScrolledViewbox = canvas.viewbox();
// when
modeling.makeProcess();
// then
expectViewbox(zoomedAndScrolledViewbox);
}));
// helpers //////////
function expectViewbox(expectedViewbox) {
return getBpmnJS().invoke(function(canvas) {
var viewbox = canvas.viewbox();
expect(viewbox.x).to.eql(expectedViewbox.x);
expect(viewbox.y).to.eql(expectedViewbox.y);
expect(viewbox.scale).to.eql(expectedViewbox.scale);
});
}
});
});
describe('features - drilldown - Legacy Processes', function() {
beforeEach(bootstrapModeler(legacyXML, { modules: testModules }));
it('should import collapsed subprocess', inject(function(canvas) {
// when
var inlineProcess1 = canvas.findRoot('inlineSubprocess_plane');
var inlineProcess2 = canvas.findRoot('inlineSubprocess_2_plane');
// then
expect(inlineProcess1).to.exist;
expect(inlineProcess2).to.exist;
}));
it('should import data associations on subprocess', inject(function(elementRegistry) {
// when
var dataInputAssociation = elementRegistry.get('DataInputAssociation_1');
var dataOutputAssociation = elementRegistry.get('DataOutputAssociation_1');
// then
expect(dataInputAssociation).to.exist;
expect(dataOutputAssociation).to.exist;
}));
it('should move inlined elements to sensible position', inject(function(elementRegistry) {
// when
var startEvent = elementRegistry.get('subprocess_startEvent');
// then
expect(startEvent).to.exist;
expect(startEvent.x).to.equal(180);
expect(startEvent.y).to.equal(160);
}));
it('should create new planes for empty processes', inject(function(canvas) {
// when
var emptyRoot = canvas.findRoot('emptyProcess_plane');
// then
expect(emptyRoot).to.exist;
}));
});
describe('a11y', function() {
it('should report no violations', inject(async function(canvas) {
// given
const container = canvas.getContainer();
// then
await expectToBeAccessible(container);
}));
});
});
describe('features/drilldown - integration', function() {
var testModules = [
coreModule,
modelingModule,
drilldownModule
];
var workingXML = require('./nested-subprocesses.bpmn');
beforeEach(bootstrapModeler(workingXML, { modules: testModules }));
describe('error handling - should handle broken DI', function() {
const subprocessMissingDi_XML = require('./subprocess-missing-di.bpmn');
const subprocessMissingBpmnDiagram_XML = require('./subprocess-missing-bpmndiagram.bpmn');
const processMissingBpmnDiagram_XML = require('./process-missing-bpmndiagram.bpmn');
const planeMissingBpmnElement_XML = require('./plane-missing-bpmnelement.bpmn');
const diagramMissingPlane_XML = require('./diagram-missing-plane.bpmn');
async function importXML(xml) {
const bpmnJS = getBpmnJS();
let result;
try {
result = await bpmnJS.importXML(xml);
} catch (error) {
result = {
error,
warnings: error.warnings
};
}
return result;
}
it('no ', async function() {
const {
error,
warnings
} = await importXML(diagramMissingPlane_XML);
// then
expect(error).not.to.exist;
expect(warnings).to.be.empty;
});
it('no ', async function() {
const {
error,
warnings
} = await importXML(planeMissingBpmnElement_XML);
// then
expect(error).not.to.exist;
expect(warnings).to.be.empty;
});
it('no for sub process', async function() {
const {
error,
warnings
} = await importXML(subprocessMissingDi_XML);
// then
expect(error).not.to.exist;
expect(warnings).to.be.empty;
});
it('no for sub process', async function() {
const {
error,
warnings
} = await importXML(subprocessMissingBpmnDiagram_XML);
// then
expect(error).not.to.exist;
expect(warnings).to.be.empty;
});
it('no for process', async function() {
const {
error,
warnings
} = await importXML(processMissingBpmnDiagram_XML);
// then
expect(error).not.to.exist;
expect(warnings).to.be.empty;
});
});
});
// helpers
function getBreadcrumbs() {
return getBpmnJS().invoke(function(canvas) {
return canvas.getContainer().querySelector('.bjs-breadcrumbs');
});
}
function expectBreadcrumbs(expected) {
var breadcrumbs = getBreadcrumbs();
var crumbs = Array.from(breadcrumbs.children).map(function(element) {
return element.innerText;
});
expect(crumbs).to.eql(expected);
}
function clickBreadcrumb(index) {
getBreadcrumbs().children[index].click();
}
================================================
FILE: test/spec/features/drilldown/collaboration-subprocesses.bpmn
================================================
sid-89A3F9F2-CCC8-46C7-816B-DD8AC8A98300
sid-89A3F9F2-CCC8-46C7-816B-DD8AC8A98300
sid-F06605E1-AEC1-4B39-8843-4AD3F547B557
sid-FC2ECAF5-771E-4ED3-BEF6-EFAB45E79500
sid-F06605E1-AEC1-4B39-8843-4AD3F547B557
sid-31F6EC44-E44C-4121-B4FE-BD69AF208C05
sid-EB275CF2-5EF1-44FA-B41B-71EB37CC2657
sid-EB275CF2-5EF1-44FA-B41B-71EB37CC2657
sid-FB543319-8DFB-4445-AAA3-720137FB230B
sid-FB543319-8DFB-4445-AAA3-720137FB230B
sid-B99D259B-1BD5-45FF-BD57-FB99C360BAC0
sid-472B540C-A0CD-46F4-9640-DF692EC1BFFC
sid-472B540C-A0CD-46F4-9640-DF692EC1BFFC
sid-910420B0-D11B-4F9D-B285-703D8AC0BA90
sid-A7460113-CB75-491D-817B-5E1A8C606B8C
sid-A7460113-CB75-491D-817B-5E1A8C606B8C
sid-01982395-64E8-43EF-A6D3-CDD276C312AA
sid-01982395-64E8-43EF-A6D3-CDD276C312AA
sid-910420B0-D11B-4F9D-B285-703D8AC0BA90
sid-B99D259B-1BD5-45FF-BD57-FB99C360BAC0
sid-FC2ECAF5-771E-4ED3-BEF6-EFAB45E79500
sid-5B23450F-AF5E-4519-B134-32107776BD44
sid-E71F5783-AFE7-44ED-8A9C-378C95087448
sid-E71F5783-AFE7-44ED-8A9C-378C95087448
sid-6B9741CD-D94B-41C7-A2EA-63A4C9445E16
sid-3BB6D6CA-BF45-4D15-A1AB-64686C41DB32
sid-3BB6D6CA-BF45-4D15-A1AB-64686C41DB32
sid-4E25B80E-EF68-4EE5-BB08-C1F54F1A7C39
sid-4E25B80E-EF68-4EE5-BB08-C1F54F1A7C39
sid-6B9741CD-D94B-41C7-A2EA-63A4C9445E16
sid-1A9DABC6-6079-4BF2-9D49-C4DC9569C519
sid-E5404926-738D-4447-87FE-FC6DD1E8BEFC
sid-E5404926-738D-4447-87FE-FC6DD1E8BEFC
sid-FED62A8F-6C3A-4BB2-8DE9-18FB0B35B50E
sid-FED62A8F-6C3A-4BB2-8DE9-18FB0B35B50E
sid-1A9DABC6-6079-4BF2-9D49-C4DC9569C519
sid-5B23450F-AF5E-4519-B134-32107776BD44
sid-31F6EC44-E44C-4121-B4FE-BD69AF208C05
sid-F7DA1903-6A1A-4858-AF4B-286A968C957F
sid-DCB98638-BEBD-4548-B501-F0E29AC71ED4
sid-F7DA1903-6A1A-4858-AF4B-286A968C957F
sid-3FAE72F2-4037-4CBA-8B89-01D7FC7FF3E3
sid-3FAE72F2-4037-4CBA-8B89-01D7FC7FF3E3
sid-DCB98638-BEBD-4548-B501-F0E29AC71ED4
================================================
FILE: test/spec/features/drilldown/diagram-missing-plane.bpmn
================================================
================================================
FILE: test/spec/features/drilldown/legacy-subprocesses.bpmn
================================================
DataObjectReference_0kmm4fv
Property_0baelcu
DataObjectReference_0kmm4fv
Flow_0obnxbt
Flow_1d6ajf7
Flow_0obnxbt
Flow_1d6ajf7
================================================
FILE: test/spec/features/drilldown/nested-subprocesses.bpmn
================================================
sid-89A3F9F2-CCC8-46C7-816B-DD8AC8A98300
sid-89A3F9F2-CCC8-46C7-816B-DD8AC8A98300
sid-F06605E1-AEC1-4B39-8843-4AD3F547B557
sid-FC2ECAF5-771E-4ED3-BEF6-EFAB45E79500
sid-F06605E1-AEC1-4B39-8843-4AD3F547B557
sid-31F6EC44-E44C-4121-B4FE-BD69AF208C05
sid-EB275CF2-5EF1-44FA-B41B-71EB37CC2657
sid-EB275CF2-5EF1-44FA-B41B-71EB37CC2657
sid-FB543319-8DFB-4445-AAA3-720137FB230B
sid-FB543319-8DFB-4445-AAA3-720137FB230B
sid-B99D259B-1BD5-45FF-BD57-FB99C360BAC0
sid-472B540C-A0CD-46F4-9640-DF692EC1BFFC
sid-472B540C-A0CD-46F4-9640-DF692EC1BFFC
sid-910420B0-D11B-4F9D-B285-703D8AC0BA90
sid-A7460113-CB75-491D-817B-5E1A8C606B8C
sid-A7460113-CB75-491D-817B-5E1A8C606B8C
sid-01982395-64E8-43EF-A6D3-CDD276C312AA
sid-01982395-64E8-43EF-A6D3-CDD276C312AA
sid-910420B0-D11B-4F9D-B285-703D8AC0BA90
sid-B99D259B-1BD5-45FF-BD57-FB99C360BAC0
sid-FC2ECAF5-771E-4ED3-BEF6-EFAB45E79500
sid-5B23450F-AF5E-4519-B134-32107776BD44
sid-E71F5783-AFE7-44ED-8A9C-378C95087448
sid-E71F5783-AFE7-44ED-8A9C-378C95087448
sid-6B9741CD-D94B-41C7-A2EA-63A4C9445E16
sid-3BB6D6CA-BF45-4D15-A1AB-64686C41DB32
sid-3BB6D6CA-BF45-4D15-A1AB-64686C41DB32
sid-4E25B80E-EF68-4EE5-BB08-C1F54F1A7C39
sid-4E25B80E-EF68-4EE5-BB08-C1F54F1A7C39
sid-6B9741CD-D94B-41C7-A2EA-63A4C9445E16
sid-1A9DABC6-6079-4BF2-9D49-C4DC9569C519
sid-E5404926-738D-4447-87FE-FC6DD1E8BEFC
sid-E5404926-738D-4447-87FE-FC6DD1E8BEFC
sid-FED62A8F-6C3A-4BB2-8DE9-18FB0B35B50E
sid-FED62A8F-6C3A-4BB2-8DE9-18FB0B35B50E
sid-1A9DABC6-6079-4BF2-9D49-C4DC9569C519
sid-5B23450F-AF5E-4519-B134-32107776BD44
sid-31F6EC44-E44C-4121-B4FE-BD69AF208C05
sid-F7DA1903-6A1A-4858-AF4B-286A968C957F
sid-DCB98638-BEBD-4548-B501-F0E29AC71ED4
sid-DCB98638-BEBD-4548-B501-F0E29AC71ED4
sid-F7DA1903-6A1A-4858-AF4B-286A968C957F
sid-3FAE72F2-4037-4CBA-8B89-01D7FC7FF3E3
sid-3FAE72F2-4037-4CBA-8B89-01D7FC7FF3E3
================================================
FILE: test/spec/features/drilldown/plane-missing-bpmnelement.bpmn
================================================
================================================
FILE: test/spec/features/drilldown/process-missing-bpmndiagram.bpmn
================================================
================================================
FILE: test/spec/features/drilldown/subprocess-missing-bpmndiagram.bpmn
================================================
================================================
FILE: test/spec/features/drilldown/subprocess-missing-di.bpmn
================================================
================================================
FILE: test/spec/features/editor-actions/BpmnEditorActionsSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
var pick = require('min-dash').pick;
var getBBox = require('diagram-js/lib/util/Elements').getBBox;
var getParent = require('lib/features/modeling/util/ModelingUtil').getParent;
import bpmnEditorActionsModule from 'lib/features/editor-actions';
import selectionModule from 'diagram-js/lib/features/selection';
import alignElementsModule from 'diagram-js/lib/features/align-elements';
import distributeElementsModule from 'diagram-js/lib/features/distribute-elements';
import modelingModule from 'lib/features/modeling';
import coreModule from 'lib/core';
import contextPad from 'lib/features/context-pad';
var basicXML = require('../../../fixtures/bpmn/nested-subprocesses.bpmn');
var collaborationXML = require('../../../fixtures/bpmn/collaboration.bpmn');
describe('features/editor-actions', function() {
describe('#moveToOrigin', function() {
function testMoveToOrigin(xml) {
return function() {
beforeEach(bootstrapModeler(xml, {
modules: [
bpmnEditorActionsModule,
modelingModule,
coreModule
]
}));
it('should move to origin', inject(function(editorActions) {
// given
var elements = editorActions.trigger('selectElements'),
boundingBox;
// when
editorActions.trigger('moveToOrigin');
boundingBox = getBBox(elements);
// then
expect(pick(boundingBox, [ 'x', 'y' ])).to.eql({ x: 0, y: 0 });
}));
};
}
describe('single process', testMoveToOrigin(basicXML));
describe('collaboration', testMoveToOrigin(collaborationXML));
describe('subprocesses', function() {
beforeEach(bootstrapModeler(basicXML, {
modules: [
bpmnEditorActionsModule,
modelingModule,
coreModule
]
}));
it('should ignore children of subprocesses', inject(
function(editorActions, elementRegistry) {
// given
var startEvent = elementRegistry.get('StartEvent_3'),
startEventParent = getParent(startEvent);
// when
editorActions.trigger('moveToOrigin');
// then
expect(getParent(startEvent)).to.equal(startEventParent);
}
));
});
});
describe('#alignElements', function() {
beforeEach(bootstrapModeler(basicXML, {
modules: [
selectionModule,
alignElementsModule,
bpmnEditorActionsModule,
modelingModule,
coreModule
]
}));
it('should align items', inject(
function(elementRegistry, selection, editorActions) {
// given
var elementIds = [ 'StartEvent_1', 'UserTask_1', 'EndEvent_1' ];
var elements = elementIds.map(function(id) {
return elementRegistry.get(id);
});
// when
selection.select(elements);
editorActions.trigger('alignElements', { type: 'middle' });
// then
expect(elements.map(function(e) {
return e.y + e.height / 2;
})).to.eql([ 311, 311, 311 ]);
}
));
it('should not align if too few elements', inject(
function(elementRegistry, eventBus, editorActions, selection) {
// given
var elementIds = [ 'StartEvent_1' ];
var elements = elementIds.map(function(id) {
return elementRegistry.get(id);
});
var changedSpy = sinon.spy();
// when
eventBus.once('commandStack.changed', changedSpy);
selection.select(elements);
editorActions.trigger('alignElements', { type: 'center' });
// then
expect(changedSpy).not.to.have.been.called;
}
));
});
describe('#distributeElements', function() {
beforeEach(bootstrapModeler(basicXML, {
modules: [
selectionModule,
distributeElementsModule,
bpmnEditorActionsModule,
modelingModule,
coreModule
]
}));
it('should distribute items', inject(
function(elementRegistry, selection, editorActions) {
// given
var elementIds = [ 'StartEvent_1', 'UserTask_1', 'EndEvent_1' ];
var elements = elementIds.map(function(id) {
return elementRegistry.get(id);
});
// when
selection.select(elements);
editorActions.trigger('distributeElements', { type: 'horizontal' });
// then
expect(elements.map(function(e) {
return e.x + e.width / 2;
})).to.eql([ 433, 574, 714 ]);
}
));
it('should not distribute if too few elements', inject(
function(elementRegistry, eventBus, editorActions, selection) {
// given
var elementIds = [ 'StartEvent_1', 'UserTask_1' ];
var elements = elementIds.map(function(id) {
return elementRegistry.get(id);
});
var changedSpy = sinon.spy();
// when
eventBus.once('commandStack.changed', changedSpy);
selection.select(elements);
editorActions.trigger('distributeElements', { type: 'horizontal' });
// then
expect(changedSpy).not.to.have.been.called;
}
));
});
describe('#replaceElement', function() {
beforeEach(bootstrapModeler(basicXML, {
modules: [
selectionModule,
bpmnEditorActionsModule,
modelingModule,
coreModule,
contextPad
]
}));
it('should open replace element', inject(function(elementRegistry, selection, editorActions, eventBus) {
// given
const element = elementRegistry.get('StartEvent_1');
selection.select(element);
var changedSpy = sinon.spy();
// when
eventBus.once('popupMenu.open', changedSpy);
editorActions.trigger('replaceElement', {});
// then
expect(changedSpy).to.have.been.called;
}));
it('should not open replace element if no selection', inject(function(editorActions, eventBus) {
// given
var changedSpy = sinon.spy();
// when
eventBus.once('popupMenu.open', changedSpy);
editorActions.trigger('replaceElement', {});
// then
expect(changedSpy).to.not.have.been.called;
}));
it('should not open replace element if multiple elements selected', inject(function(elementRegistry, selection, editorActions, eventBus) {
// given
var elementIds = [ 'StartEvent_1', 'UserTask_1' ];
var elements = elementIds.map(function(id) {
return elementRegistry.get(id);
});
selection.select(elements);
var changedSpy = sinon.spy();
// when
eventBus.once('popupMenu.open', changedSpy);
editorActions.trigger('replaceElement', {});
// then
expect(changedSpy).to.not.have.been.called;
}));
});
});
================================================
FILE: test/spec/features/grid-snapping/BpmnGridSnapping.bpmn
================================================
TextAnnotation_1
SequenceFlow_0e7hjjz
SequenceFlow_0jhtpzs
SequenceFlow_0e7hjjz
SequenceFlow_0jhtpzs
================================================
FILE: test/spec/features/grid-snapping/BpmnGridSnappingSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import coreModule from 'lib/core';
import createModule from 'diagram-js/lib/features/create';
import gridSnappingModule from 'lib/features/grid-snapping';
import modelingModule from 'lib/features/modeling';
import moveModule from 'diagram-js/lib/features/move';
import {
createCanvasEvent as canvasEvent
} from '../../../util/MockEvents';
import { getMid } from 'diagram-js/lib/layout/LayoutUtil';
import {
isString,
pick,
assign
} from 'min-dash';
var LOW_PRIORITY = 500;
describe('features/grid-snapping', function() {
describe('basics', function() {
describe('create', function() {
var diagramXML = require('./basic.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: [
coreModule,
createModule,
gridSnappingModule,
modelingModule,
moveModule
]
}));
it('start event', inject(function(canvas, create, dragging, elementFactory, eventBus) {
// given
var rootElement = canvas.getRootElement(),
rootGfx = canvas.getGraphics(rootElement);
var startEvent = elementFactory.createShape({ type: 'bpmn:StartEvent' });
var events = recordEvents(eventBus, [
'create.move',
'create.end'
]);
// when
create.start(canvasEvent({ x: 100, y: 100 }), startEvent);
dragging.hover({ element: rootElement, gfx: rootGfx });
dragging.move(canvasEvent({ x: 106, y: 112 }));
dragging.move(canvasEvent({ x: 112, y: 124 }));
dragging.move(canvasEvent({ x: 118, y: 136 }));
dragging.move(canvasEvent({ x: 124, y: 148 }));
dragging.move(canvasEvent({ x: 130, y: 160 }));
dragging.end();
// then
expect(events.map(position)).to.eql([
{ x: 100, y: 100 }, // move (triggered on create.start thanks to autoActivate)
{ x: 110, y: 110 }, // move
{ x: 110, y: 120 }, // move
{ x: 120, y: 140 }, // move
{ x: 120, y: 150 }, // move
{ x: 130, y: 160 }, // move
{ x: 130, y: 160 } // end
]);
// expect snapped
expect(getMid(startEvent)).to.eql({
x: 130,
y: 160
});
}));
});
});
describe('snap top-left on move', function() {
var diagramXML = require('./BpmnGridSnapping.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: [
coreModule,
createModule,
gridSnappingModule,
modelingModule,
moveModule,
gridSnappingModule
]
}));
var participant,
subProcess,
textAnnotation;
beforeEach(inject(function(elementRegistry) {
participant = elementRegistry.get('Participant_1');
subProcess = elementRegistry.get('SubProcess_1');
textAnnotation = elementRegistry.get('TextAnnotation_1');
}));
it('participant', inject(function(dragging, eventBus, move) {
// given
var events = recordEvents(eventBus, [
'shape.move.move',
'shape.move.end'
]);
// when
move.start(canvasEvent({ x: 100, y: 100 }), participant);
dragging.move(canvasEvent({ x: 106, y: 112 }));
dragging.move(canvasEvent({ x: 112, y: 124 }));
dragging.move(canvasEvent({ x: 118, y: 136 }));
dragging.move(canvasEvent({ x: 124, y: 148 }));
dragging.move(canvasEvent({ x: 130, y: 160 }));
dragging.end();
// then
expect(events.map(position('top-left'))).to.eql([
{ x: 110, y: 110 }, // move
{ x: 110, y: 120 }, // move
{ x: 120, y: 140 }, // move
{ x: 120, y: 150 }, // move
{ x: 130, y: 160 }, // move
{ x: 130, y: 160 } // end
]);
// expect snapped to top-left
expect(participant.x).to.equal(130);
expect(participant.y).to.equal(160);
}));
it('sub process', inject(function(dragging, eventBus, move) {
// given
var events = recordEvents(eventBus, [
'shape.move.move',
'shape.move.end'
]);
// when
move.start(canvasEvent({ x: 150, y: 130 }), subProcess);
dragging.move(canvasEvent({ x: 156, y: 142 }));
dragging.move(canvasEvent({ x: 162, y: 154 }));
dragging.move(canvasEvent({ x: 168, y: 166 }));
dragging.move(canvasEvent({ x: 174, y: 178 }));
dragging.move(canvasEvent({ x: 180, y: 190 }));
dragging.end();
// then
expect(events.map(position('top-left'))).to.eql([
{ x: 160, y: 140 }, // move
{ x: 160, y: 150 }, // move
{ x: 170, y: 170 }, // move
{ x: 170, y: 180 }, // move
{ x: 180, y: 190 }, // move
{ x: 180, y: 190 } // end
]);
// expect snapped to top-left
expect(subProcess.x).to.equal(180);
expect(subProcess.y).to.equal(190);
}));
it('text annotation', inject(function(dragging, eventBus, move) {
// given
var events = recordEvents(eventBus, [
'shape.move.move',
'shape.move.end'
]);
// when
move.start(canvasEvent({ x: 700, y: 20 }), textAnnotation);
dragging.move(canvasEvent({ x: 706, y: 32 }));
dragging.move(canvasEvent({ x: 712, y: 44 }));
dragging.move(canvasEvent({ x: 718, y: 56 }));
dragging.move(canvasEvent({ x: 724, y: 68 }));
dragging.move(canvasEvent({ x: 730, y: 80 }));
dragging.end();
// 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
]);
// expect snapped to top-left
expect(textAnnotation.x).to.equal(730);
expect(textAnnotation.y).to.equal(80);
}));
});
describe('auto resize on toggle collapse', function() {
var diagramXML = require('./BpmnGridSnapping.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: [
coreModule,
createModule,
gridSnappingModule,
modelingModule,
moveModule,
gridSnappingModule
]
}));
var participant,
subProcess;
beforeEach(inject(function(elementRegistry) {
participant = elementRegistry.get('Participant_1');
subProcess = elementRegistry.get('SubProcess_1');
}));
describe('collapsing', function() {
it('participant (no auto resize)', inject(function(bpmnReplace) {
// given
var collapsedBounds = assign(
getBounds(participant),
{ height: 60 }
);
// when
var collapsedParticipant = bpmnReplace.replaceElement(participant,
{
type: 'bpmn:Participant',
isExpanded: false
}
);
// then
expect(collapsedParticipant).to.have.bounds(collapsedBounds);
}));
it('sub process (no auto resize)', inject(function(bpmnReplace) {
// given
var mid = getMid(subProcess);
// when
var collapsedSubProcess = bpmnReplace.replaceElement(subProcess,
{
type: 'bpmn:SubProcess',
isExpanded: false
}
);
// then
expect(getMid(collapsedSubProcess)).to.eql(mid);
expect(collapsedSubProcess).to.include({
width: 100,
height: 80
});
}));
});
describe('expanding', function() {
it('participant (auto resize )', inject(function(bpmnReplace) {
// given
var bounds = getBounds(participant);
var collapsedParticipant = bpmnReplace.replaceElement(participant,
{
type: 'bpmn:Participant',
isExpanded: false
}
);
// when
var expandedParticipant = bpmnReplace.replaceElement(collapsedParticipant,
{
type: 'bpmn:Participant',
isExpanded: true
}
);
// then
expect(expandedParticipant).to.have.bounds(bounds);
}));
it('sub process (auto resize )', inject(function(bpmnReplace) {
// given
var collapsedSubProcess = bpmnReplace.replaceElement(subProcess,
{
type: 'bpmn:SubProcess',
isExpanded: false
}
);
// when
var expandedSubProcess = bpmnReplace.replaceElement(collapsedSubProcess,
{
type: 'bpmn:SubProcess',
isExpanded: true
}
);
// then
expect(expandedSubProcess).to.include({
x: 150,
y: 120,
width: 360,
height: 210
});
}));
});
});
});
// helpers //////////
function recordEvents(eventBus, eventTypes) {
var events = [];
eventTypes.forEach(function(eventType) {
eventBus.on(eventType, LOW_PRIORITY, function(event) {
events.push(event);
});
});
return events;
}
/**
* Returns x and y of an event. If called with string that specifies orientation if will return
* x and y of specified orientation.
*
* @param {Object|string} event - Event or orientation
*
* @return {Object}
*/
function position(event) {
var orientation;
if (isString(event)) {
orientation = event;
return function(event) {
var shape = event.shape;
var x = event.x,
y = event.y;
if (/top/.test(orientation)) {
y -= shape.height / 2;
}
if (/right/.test(orientation)) {
x += shape.width / 2;
}
if (/bottom/.test(orientation)) {
y += shape.height / 2;
}
if (/left/.test(orientation)) {
x -= shape.width / 2;
}
return {
x: x,
y: y
};
};
}
return {
x: event.x,
y: event.y
};
}
function getBounds(shape) {
return pick(shape, [ 'x', 'y', 'width', 'height' ]);
}
================================================
FILE: test/spec/features/grid-snapping/basic.bpmn
================================================
================================================
FILE: test/spec/features/grid-snapping/behavior/AutoPlaceBehavior.bpmn
================================================
================================================
FILE: test/spec/features/grid-snapping/behavior/AutoPlaceBehaviorSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import { getMid } from 'diagram-js/lib/layout/LayoutUtil';
import autoPlaceModule from 'lib/features/auto-place';
import coreModule from 'lib/core';
import gridSnappingModule from 'lib/features/grid-snapping';
import modelingModule from 'lib/features/modeling';
describe('features/grid-snapping - auto-place', function() {
var diagramXML = require('./AutoPlaceBehavior.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: [
autoPlaceModule,
coreModule,
gridSnappingModule,
modelingModule
]
}));
describe('flow node', function() {
it('without existing elements', inject(function(autoPlace, elementFactory, elementRegistry) {
// given
var shape1 = elementFactory.createShape({
id: 'Task_2',
type: 'bpmn:Task'
});
var source = elementRegistry.get('StartEvent_1');
// when
autoPlace.append(source, shape1);
// then
shape1 = elementRegistry.get('Task_2');
expect(getMid(shape1)).to.eql({
x: 220, // 218 snapped to 220
y: 105 // not snapped
});
}));
it('with existing elements', inject(function(autoPlace, elementFactory, elementRegistry) {
// given
var shape1 = elementFactory.createShape({
id: 'Task_2',
type: 'bpmn:Task'
});
var source = elementRegistry.get('StartEvent_1');
autoPlace.append(source, shape1);
var shape2 = elementFactory.createShape({
id: 'Task_3',
type: 'bpmn:Task'
});
// when
autoPlace.append(source, shape2);
// then
shape2 = elementRegistry.get('Task_3');
expect(getMid(shape2)).to.eql({
x: 220, // 220 snapped to 220
y: 220 // 215 snapped to 220
});
}));
});
describe('text annotation', function() {
it('without existing elements', inject(function(autoPlace, elementFactory, elementRegistry) {
// given
var shape1 = elementFactory.createShape({
id: 'TextAnnotation_1',
type: 'bpmn:TextAnnotation'
});
var source = elementRegistry.get('StartEvent_1');
// when
autoPlace.append(source, shape1);
// then
shape1 = elementRegistry.get('TextAnnotation_1');
expect(getMid(shape1)).to.eql({
x: 170, // 168 snapped to 170
y: 15 // 22 snapped to 15
});
}));
it('with existing elements', inject(function(autoPlace, elementFactory, elementRegistry) {
// given
var shape1 = elementFactory.createShape({
id: 'TextAnnotation_1',
type: 'bpmn:TextAnnotation'
});
var source = elementRegistry.get('StartEvent_1');
autoPlace.append(source, shape1);
var shape2 = elementFactory.createShape({
id: 'TextAnnotation_2',
type: 'bpmn:TextAnnotation'
});
// when
autoPlace.append(source, shape2);
// then
shape2 = elementRegistry.get('TextAnnotation_2');
expect(getMid(shape2)).to.eql({
x: 170, // 168 snapped to 170
y: -45 // -45 snapped to -45
});
}));
});
describe('data object/store reference', function() {
it('without existing elements', inject(function(autoPlace, elementFactory, elementRegistry) {
// given
var shape1 = elementFactory.createShape({
id: 'DataObjectReference_1',
type: 'bpmn:DataObjectReference'
});
var source = elementRegistry.get('Task_1');
// when
autoPlace.append(source, shape1);
// then
shape1 = elementRegistry.get('DataObjectReference_1');
expect(getMid(shape1)).to.eql({
x: 160, // 158 snapped to 160
y: 400 // 398 snapped to 400
});
}));
it('with existing elements', inject(function(autoPlace, elementFactory, elementRegistry) {
// given
var shape1 = elementFactory.createShape({
id: 'DataObjectReference_1',
type: 'bpmn:DataObjectReference'
});
var source = elementRegistry.get('Task_1');
autoPlace.append(source, shape1);
var shape2 = elementFactory.createShape({
id: 'DataObjectReference_2',
type: 'bpmn:DataObjectReference'
});
// when
autoPlace.append(source, shape2);
// then
shape2 = elementRegistry.get('DataObjectReference_2');
expect(getMid(shape2)).to.eql({
x: 230, // 226 snapped to 230
y: 400 // 398 snapped to 400
});
}));
});
});
================================================
FILE: test/spec/features/grid-snapping/behavior/AutoResizeBehavior.bpmn
================================================
================================================
FILE: test/spec/features/grid-snapping/behavior/AutoResizeBehaviorSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import autoResizeModule from 'lib/features/auto-resize';
import coreModule from 'lib/core';
import gridSnappingModule from 'lib/features/grid-snapping';
import modelingModule from 'lib/features/modeling';
describe('features/grid-snapping - auto-resize', function() {
var diagramXML = require('./AutoResizeBehavior.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: [
autoResizeModule,
coreModule,
gridSnappingModule,
modelingModule
]
}));
it('should snap (width, height)', inject(function(elementRegistry, modeling) {
// given
var subProcess = elementRegistry.get('SubProcess_1'),
task = elementRegistry.get('Task_1');
[
{ x: 100, y: 100, width: 350, height: 270 },
{ x: 100, y: 100, width: 450, height: 270 },
{ x: 100, y: 100, width: 450, height: 360 },
{ x: 100, y: 100, width: 450, height: 360 },
{ x: 100, y: 100, width: 560, height: 460 }
].forEach(function(bounds) {
// when
modeling.moveElements([ task ], { x: 36, y: 48 }, subProcess);
// then
expect(subProcess).to.have.bounds(bounds);
});
}));
});
================================================
FILE: test/spec/features/grid-snapping/behavior/CreateParticipantBehavior.bpmn
================================================
SequenceFlow_1
SequenceFlow_1
SequenceFlow_2
SequenceFlow_2
SequenceFlow_3
SequenceFlow_3
================================================
FILE: test/spec/features/grid-snapping/behavior/CreateParticipantBehaviorSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import coreModule from 'lib/core';
import createModule from 'diagram-js/lib/features/create';
import gridSnappingModule from 'lib/features/grid-snapping';
import modelingModule from 'lib/features/modeling';
import { createCanvasEvent as canvasEvent } from '../../../../util/MockEvents';
describe('features/grid-snapping - create participant', function() {
var diagramXML = require('./CreateParticipantBehavior.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: [
coreModule,
createModule,
gridSnappingModule,
modelingModule
]
}));
it('should snap width and height', inject(
function(create, dragging, elementFactory, elementRegistry) {
// given
var process = elementRegistry.get('Process_1'),
processGfx = elementRegistry.getGraphics(process);
var participant = elementFactory.createParticipantShape();
// when
create.start(canvasEvent({ x: 100, y: 100 }), participant);
dragging.hover({ element: process, gfx: processGfx });
dragging.move(canvasEvent({ x: 100, y: 100 }));
dragging.end();
// then
expect(participant.width).to.equal(610);
expect(participant.height).to.equal(340);
}
));
});
================================================
FILE: test/spec/features/grid-snapping/behavior/LayoutConnectionBehavior.bpmn
================================================
SequenceFlow_1
SequenceFlow_1
================================================
FILE: test/spec/features/grid-snapping/behavior/LayoutConnectionBehaviorSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import coreModule from 'lib/core';
import gridSnappingModule from 'lib/features/grid-snapping';
import modelingModule from 'lib/features/modeling';
import moveModule from 'diagram-js/lib/features/move';
import copyPasteModule from 'lib/features/copy-paste';
describe('features/grid-snapping - layout connection', function() {
describe('on connection create', function() {
var diagramXML = require('./LayoutConnectionBehavior.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: [
coreModule,
gridSnappingModule,
modelingModule,
moveModule
]
}));
it('should snap 3 segment connection (1 middle segment)', inject(
function(elementRegistry, modeling) {
// given
var task1 = elementRegistry.get('Task_1'),
task2 = elementRegistry.get('Task_2');
// when
var connection = modeling.connect(task1, task2);
// then
expect(connection.waypoints[1]).to.eql({ x: 250, y: 140 });
expect(connection.waypoints[2]).to.eql({ x: 250, y: 240 });
})
);
it('should snap 4 segment connection (2 middle segments)', inject(
function(elementRegistry, modeling) {
// given
var boundaryEvent1 = elementRegistry.get('BoundaryEvent_1'),
task4 = elementRegistry.get('Task_4');
// when
var connection = modeling.connect(boundaryEvent1, task4);
// then
expect(connection.waypoints[1]).to.eql({ x: 150, y: 520 });
expect(connection.waypoints[2]).to.eql({ x: 230, y: 520 });
expect(connection.waypoints[3]).to.eql({ x: 230, y: 440 });
}
));
});
describe('on connection layout', function() {
var diagramXML = require('./LayoutConnectionBehavior.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: [
coreModule,
gridSnappingModule,
modelingModule,
moveModule
]
}));
var task1, task2, connection;
beforeEach(inject(function(elementRegistry, modeling) {
// given
task1 = elementRegistry.get('Task_1'),
task2 = elementRegistry.get('Task_2');
connection = modeling.connect(task1, task2);
}));
it('should NOT snap on reconnect start', inject(function(modeling) {
// when
modeling.moveElements([ task1 ], { x: 50, y: 50 });
// then
expect(connection.waypoints[1]).to.eql({ x: 275, y: 190 });
expect(connection.waypoints[2]).to.eql({ x: 275, y: 240 });
}));
it('should NOT snap on reconnect end', inject(function(modeling) {
// when
modeling.moveElements([ task2 ], { x: -50, y: -50 });
// then
expect(connection.waypoints[1]).to.eql({ x: 225, y: 140 });
expect(connection.waypoints[2]).to.eql({ x: 225, y: 190 });
}));
it('should snap', inject(function(modeling, elementRegistry) {
// given
var flow = elementRegistry.get('SequenceFlow_1');
// when
modeling.layoutConnection(flow);
// then
expect(flow.waypoints[1]).to.eql({ x: 530, y: 242 });
expect(flow.waypoints[2]).to.eql({ x: 530, y: 310 });
}));
it('should UNDO snap', inject(function(modeling, commandStack, elementRegistry) {
// given
var flow = elementRegistry.get('SequenceFlow_1');
modeling.layoutConnection(flow);
// when
commandStack.undo();
// then
expect(flow.waypoints[1]).to.eql({ x: 526, y: 242 });
expect(flow.waypoints[2]).to.eql({ x: 526, y: 310 });
}));
});
describe('on paste multiple', function() {
var diagramXML = require('./LayoutConnectionBehavior.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: [
coreModule,
gridSnappingModule,
modelingModule,
moveModule,
copyPasteModule
]
}));
it('should not update waypoints', inject(
function(canvas, eventBus, copyPaste, elementRegistry) {
// given
var layoutSpy = sinon.spy();
copyPaste.copy([
elementRegistry.get('Task_2'),
elementRegistry.get('SequenceFlow_1'),
elementRegistry.get('Task_5')
]);
eventBus.on('commandStack.connection.updateWaypoints.execute', layoutSpy);
// when
copyPaste.paste({
element: canvas.getRootElement(),
point: {
x: 100,
y: 200
}
});
// then
expect(layoutSpy).not.to.have.been.called;
}
));
});
});
================================================
FILE: test/spec/features/interaction-events/BpmnInteractionEventsSpec.js
================================================
import {
queryAll as domQueryAll
} from 'min-dom';
import {
getBpmnJS,
bootstrapModeler,
inject
} from 'test/TestHelper';
import modelingModule from 'lib/features/modeling';
import coreModule from 'lib/core';
import interactionEventsModule from 'lib/features/interaction-events';
import createModule from 'diagram-js/lib/features/create';
import moveModule from 'diagram-js/lib/features/move';
var testModules = [
coreModule,
modelingModule,
interactionEventsModule,
createModule,
moveModule
];
var HIT_ALL_CLS = 'djs-hit-all';
var HIT_CLICK_STROKE_CLS = 'djs-hit-click-stroke';
var HIT_NO_MOVE_CLS = 'djs-hit-no-move';
describe('features/interaction-events', function() {
describe('participant hits', function() {
var diagramXML = require('test/fixtures/bpmn/collaboration.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
beforeEach(inject(function(dragging) {
dragging.setOptions({ manual: true });
}));
it('should create THREE hit zones per participant', inject(function(elementRegistry) {
// given
var participant = elementRegistry.get('Participant_1');
var hitZones = getHitZones(participant);
// then
expectToHaveChildren(HIT_ALL_CLS, 1, participant);
expectToHaveChildren(HIT_CLICK_STROKE_CLS, 1, participant);
expectToHaveChildren(HIT_NO_MOVE_CLS, 1, participant);
expectSize(hitZones.all[0], { width: 30, height: participant.height });
expectSize(hitZones.click[0], { width: participant.width, height: participant.height });
expectSize(hitZones.noMove[0], { width: participant.width, height: participant.height });
}));
it('should create THREE hit zones per lane', inject(function(elementRegistry) {
// given
var lane = elementRegistry.get('Lane_1');
var hitZones = getHitZones(lane);
// then
expectToHaveChildren(HIT_ALL_CLS, 1, lane);
expectToHaveChildren(HIT_CLICK_STROKE_CLS, 1, lane);
expectToHaveChildren(HIT_NO_MOVE_CLS, 1, lane);
expectSize(hitZones.all[0], { width: 30, height: lane.height });
expectSize(hitZones.click[0], { width: lane.width, height: lane.height });
expectSize(hitZones.noMove[0], { width: lane.width, height: lane.height });
}));
it('should create one hit zone per collapsed participant',
inject(function(elementRegistry, bpmnReplace) {
// given
var participant = elementRegistry.get('Participant_1');
// when
var collapsedParticipant = bpmnReplace.replaceElement(participant, {
type: 'bpmn:Participant',
isExpanded: false
});
var hitZones = getHitZones(collapsedParticipant);
// then
expectToHaveChildren(HIT_ALL_CLS, 1, collapsedParticipant);
expectSize(hitZones.all[0], { width: collapsedParticipant.width, height: collapsedParticipant.height });
})
);
});
describe('vertical participant hits', function() {
var diagramXML = require('test/fixtures/bpmn/collaboration-vertical.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
beforeEach(inject(function(dragging) {
dragging.setOptions({ manual: true });
}));
it('should create THREE hit zones per participant', inject(function(elementRegistry) {
// given
var participant = elementRegistry.get('V_Participant_1');
var hitZones = getHitZones(participant);
// then
expectToHaveChildren(HIT_ALL_CLS, 1, participant);
expectToHaveChildren(HIT_CLICK_STROKE_CLS, 1, participant);
expectToHaveChildren(HIT_NO_MOVE_CLS, 1, participant);
expectSize(hitZones.all[0], { width: participant.width, height: 30 });
expectSize(hitZones.click[0], { width: participant.width, height: participant.height });
expectSize(hitZones.noMove[0], { width: participant.width, height: participant.height });
}));
it('should create THREE hit zones per lane', inject(function(elementRegistry) {
// given
var lane = elementRegistry.get('V_Lane_1');
var hitZones = getHitZones(lane);
// then
expectToHaveChildren(HIT_ALL_CLS, 1, lane);
expectToHaveChildren(HIT_CLICK_STROKE_CLS, 1, lane);
expectToHaveChildren(HIT_NO_MOVE_CLS, 1, lane);
expectSize(hitZones.all[0], { width: lane.width, height: 30 });
expectSize(hitZones.click[0], { width: lane.width, height: lane.height });
expectSize(hitZones.noMove[0], { width: lane.width, height: lane.height });
}));
it('should create one hit zone per collapsed participant',
inject(function(elementRegistry, bpmnReplace) {
// given
var participant = elementRegistry.get('V_Participant_1');
// when
var collapsedParticipant = bpmnReplace.replaceElement(participant, {
type: 'bpmn:Participant',
isExpanded: false
});
var hitZones = getHitZones(collapsedParticipant);
// then
expectToHaveChildren(HIT_ALL_CLS, 1, collapsedParticipant);
expectSize(hitZones.all[0], { width: collapsedParticipant.width, height: collapsedParticipant.height });
})
);
});
describe('sub process hits', function() {
var diagramXML = require('test/fixtures/bpmn/containers.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
beforeEach(inject(function(dragging) {
dragging.setOptions({ manual: true });
}));
it('should create THREE hit zones per sub process', inject(function(elementRegistry) {
// given
var subProcess = elementRegistry.get('SubProcess_1');
// then
expectToHaveChildren(HIT_ALL_CLS, 1, subProcess);
expectToHaveChildren(HIT_CLICK_STROKE_CLS, 1, subProcess);
expectToHaveChildren(HIT_NO_MOVE_CLS, 1, subProcess);
}));
it('should create one hit zone per collapsed sub process',
inject(function(elementRegistry, bpmnReplace) {
// given
var subProcess = elementRegistry.get('SubProcess_1');
// when
var collapsedSubProcess = bpmnReplace.replaceElement(subProcess, {
type: 'bpmn:SubProcess',
isExpanded: false
});
// then
expectToHaveChildren(HIT_ALL_CLS, 1, collapsedSubProcess);
expectToHaveChildren(HIT_CLICK_STROKE_CLS, 0, collapsedSubProcess);
})
);
});
});
// helper ///////////
function expectToHaveChildren(className, expectedCount, element) {
var selector = '.' + className;
var elementRegistry = getBpmnJS().get('elementRegistry'),
gfx = elementRegistry.getGraphics(element),
realCount = domQueryAll(selector, gfx).length;
expect(
realCount,
'expected ' + element.id + ' to have ' + expectedCount +
' children mat ' + selector + ' but got ' + realCount
).to.eql(expectedCount);
}
function getHitZones(element) {
var elementRegistry = getBpmnJS().get('elementRegistry'),
gfx = elementRegistry.getGraphics(element);
return {
all: domQueryAll('.' + HIT_ALL_CLS, gfx),
click: domQueryAll('.' + HIT_CLICK_STROKE_CLS, gfx),
noMove: domQueryAll('.' + HIT_NO_MOVE_CLS, gfx)
};
}
function expectSize(element, expectedSize) {
var size = getSize(element);
expect(size.width).to.eql(expectedSize.width);
expect(size.height).to.eql(expectedSize.height);
}
function getSize(element) {
const bbox = element.getBBox();
return {
width: bbox.width,
height: bbox.height
};
}
================================================
FILE: test/spec/features/keyboard/BpmnKeyboardBindingsSpec.js
================================================
import {
bootstrapViewer,
inject
} from 'test/TestHelper';
import { forEach } from 'min-dash';
import copyPasteModule from 'lib/features/copy-paste';
import coreModule from 'lib/core';
import editorActionsModule from 'lib/features/editor-actions';
import globalConnectModule from 'diagram-js/lib/features/global-connect';
import handToolModule from 'diagram-js/lib/features/hand-tool';
import keyboardModule from 'lib/features/keyboard';
import labelEditingModule from 'lib/features/label-editing';
import lassoToolModule from 'diagram-js/lib/features/lasso-tool';
import modelingModule from 'lib/features/modeling';
import searchModule from 'lib/features/search';
import spaceToolModule from 'diagram-js/lib/features/space-tool';
import popupMenu from 'diagram-js/lib/features/popup-menu';
import contextPad from 'lib/features/context-pad';
import {
createKeyEvent
} from 'test/util/KeyEvents';
describe('features/keyboard', function() {
var diagramXML = require('../../../fixtures/bpmn/simple.bpmn');
var testModules = [
copyPasteModule,
coreModule,
editorActionsModule,
globalConnectModule,
handToolModule,
keyboardModule,
labelEditingModule,
lassoToolModule,
modelingModule,
searchModule,
spaceToolModule,
popupMenu,
contextPad
];
beforeEach(bootstrapViewer(diagramXML, { modules: testModules }));
describe('bpmn keyboard bindings', function() {
it('should include triggers inside editorActions', inject(function(editorActions) {
// given
var expectedActions = [
'undo',
'redo',
'copy',
'duplicate',
'paste',
'cut',
'zoom',
'removeSelection',
'selectElements',
'spaceTool',
'lassoTool',
'handTool',
'globalConnectTool',
'setColor',
'directEditing',
'find',
'moveToOrigin',
'replaceElement'
];
// then
expect(editorActions.getActions()).to.eql(expectedActions);
}));
forEach([ 'c', 'C' ], function(key) {
it('should global connect tool for key ' + key, inject(function(keyboard, globalConnect) {
sinon.spy(globalConnect, 'toggle');
// given
var e = createKeyEvent(key);
// when
keyboard._keyHandler(e);
// then
expect(globalConnect.toggle).to.have.been.calledOnce;
}));
});
forEach([ 'l', 'L' ], function(key) {
it('should trigger lasso tool for key ' + key, inject(function(keyboard, lassoTool) {
sinon.spy(lassoTool, 'activateSelection');
// given
var e = createKeyEvent(key);
// when
keyboard._keyHandler(e);
// then
expect(lassoTool.activateSelection).to.have.been.calledOnce;
}));
});
forEach([ 's', 'S' ], function(key) {
it('should trigger space tool', inject(function(keyboard, spaceTool) {
sinon.spy(spaceTool, 'activateSelection');
// given
var e = createKeyEvent(key);
// when
keyboard._keyHandler(e);
// then
expect(spaceTool.activateSelection).to.have.been.calledOnce;
}));
});
forEach([ 'e', 'E' ], function(key) {
it('should trigger direct editing', inject(function(keyboard, selection, elementRegistry, directEditing) {
sinon.spy(directEditing, 'activate');
// given
var task = elementRegistry.get('Task_1');
selection.select(task);
var e = createKeyEvent(key);
// when
keyboard._keyHandler(e);
// then
expect(directEditing.activate).to.have.been.calledOnce;
}));
});
forEach([ 'a', 'A' ], function(key) {
it('should select all elements',
inject(function(canvas, keyboard, selection, elementRegistry) {
// given
var e = createKeyEvent(key, { ctrlKey: true });
var allElements = elementRegistry.getAll(),
rootElement = canvas.getRootElement();
// when
keyboard._keyHandler(e);
// then
var selectedElements = selection.get();
expect(selectedElements).to.have.length(allElements.length - 1);
expect(selectedElements).not.to.contain(rootElement);
})
);
});
forEach([ 'f', 'F' ], function(key) {
it('should trigger search for labels', inject(function(keyboard, searchPad) {
sinon.spy(searchPad, 'toggle');
// given
var e = createKeyEvent(key, { ctrlKey: true });
// when
keyboard._keyHandler(e);
// then
expect(searchPad.toggle).to.have.been.calledOnce;
}));
});
forEach([ 'r', 'R' ], function(key) {
it('should trigger replace menu', inject(function(keyboard, popupMenu, elementRegistry, selection) {
sinon.spy(popupMenu, 'open');
// given
var task = elementRegistry.get('Task_1');
selection.select(task);
var e = createKeyEvent(key);
// when
keyboard._keyHandler(e);
// then
expect(popupMenu.open).to.have.been.calledOnce;
}));
});
});
});
================================================
FILE: test/spec/features/keyboard-move-selection/KeyboardMoveSelectionSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import coreModule from 'lib/core';
import keyboardMoveSelectionModule from 'diagram-js/lib/features/keyboard-move-selection';
import modelingModule from 'lib/features/modeling';
import rulesModule from 'lib/features/rules';
import { getMid } from 'diagram-js/lib/layout/LayoutUtil';
describe('features/keyboard-move-selection', function() {
var diagramXML = require('./keyboard-move-selection.bpmn');
var testModules = [
coreModule,
keyboardMoveSelectionModule,
modelingModule,
rulesModule
];
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
it('should move task', inject(function(elementRegistry, keyboardMoveSelection, selection) {
// given
var task = elementRegistry.get('Task_1');
selection.select(task);
var mid = getMid(task);
// when
keyboardMoveSelection.moveSelection('right');
// then
expect(getMid(task)).not.to.eql(mid);
}));
it('should move participant', inject(function(elementRegistry, keyboardMoveSelection, selection) {
// given
var participant = elementRegistry.get('Participant_1');
selection.select(participant);
var mid = getMid(participant);
// when
keyboardMoveSelection.moveSelection('right');
// then
expect(getMid(participant)).not.to.eql(mid);
}));
it('should NOT move lane', inject(function(elementRegistry, keyboardMoveSelection, selection) {
// given
var lane = elementRegistry.get('Lane_1');
selection.select(lane);
var mid = getMid(lane);
// when
keyboardMoveSelection.moveSelection('right');
// then
expect(getMid(lane)).to.eql(mid);
}));
it('should NOT move boundary event without host', inject(
function(elementRegistry, keyboardMoveSelection, selection, rules) {
// given
var boundaryEvent = elementRegistry.get('BoundaryEvent_on_Task');
selection.select(boundaryEvent);
var mid = getMid(boundaryEvent);
var hostBeforeMove = boundaryEvent.host;
// when
keyboardMoveSelection.moveSelection('right');
keyboardMoveSelection.moveSelection('right');
keyboardMoveSelection.moveSelection('right');
// then
// position should not change
expect(getMid(boundaryEvent)).to.eql(mid);
// shouldn't be deattached from host
expect(boundaryEvent.host).to.equal(hostBeforeMove);
}
));
it('should move boundary event with host', inject(
function(elementRegistry, keyboardMoveSelection, selection) {
// given
var task = elementRegistry.get('Task_1');
var boundaryEvent = elementRegistry.get('BoundaryEvent_on_Task');
selection.select([ task, boundaryEvent ]);
var taskMid = getMid(task);
var boundaryMid = getMid(boundaryEvent);
// when
keyboardMoveSelection.moveSelection('right');
// then
expect(getMid(task)).not.to.eql(taskMid);
expect(getMid(boundaryEvent)).not.to.eql(boundaryMid);
}
));
});
================================================
FILE: test/spec/features/keyboard-move-selection/keyboard-move-selection.bpmn
================================================
Task_1
================================================
FILE: test/spec/features/label-editing/LabelEditing.bpmn
================================================
FOO
StartEvent_1
SubProcess_1
StartEvent_08jn2xd
Task_1
Task_1fo1fvh
ExclusiveGateway_1
EndEvent_1
SubProcess_2
DataInput
DataOutput
SequenceFlow_2
SequenceFlow_2
SequenceFlow_14kuv1e
SequenceFlow_1
SequenceFlow_1
SequenceFlow_14kuv1e
SequenceFlow_13rjp44
SequenceFlow_0kmqqm9
SequenceFlow_13rjp44
SequenceFlow_1h7vuvi
SequenceFlow_1h7vuvi
SequenceFlow_0kmqqm9
================================================
FILE: test/spec/features/label-editing/LabelEditingPreviewSpec.js
================================================
import {
bootstrapViewer,
inject
} from 'test/TestHelper';
var pick = require('min-dash').pick;
import labelEditingModule from 'lib/features/label-editing';
import coreModule from 'lib/core';
import draggingModule from 'diagram-js/lib/features/dragging';
import modelingModule from 'lib/features/modeling';
describe('features - label-editing preview', function() {
var diagramXML = require('./LabelEditing.bpmn');
beforeEach(bootstrapViewer(diagramXML, {
modules: [
labelEditingModule,
coreModule,
draggingModule,
modelingModule
]
}));
describe('activate', function() {
it('[external labels AND text annotations ] should add marker to hide element on activate', inject(
function(directEditing, elementRegistry) {
// given
var textAnnotation = elementRegistry.get('TextAnnotation_1');
// when
directEditing.activate(textAnnotation);
// then
var gfx = elementRegistry.getGraphics(textAnnotation);
expect(gfx.classList.contains('djs-element-hidden')).to.be.true;
}
));
it('[internal labels] should add marker to hide label on activate', inject(
function(directEditing, elementRegistry) {
// given
var task = elementRegistry.get('Task_1');
// when
directEditing.activate(task);
// then
var gfx = elementRegistry.getGraphics(task);
expect(gfx.classList.contains('djs-label-hidden')).to.be.true;
}
));
});
describe('resize', function() {
it('[text annotations] should resize preview on resize', inject(
function(directEditing, elementRegistry, eventBus, labelEditingPreview) {
// given
var textAnnotation = elementRegistry.get('TextAnnotation_1');
directEditing.activate(textAnnotation);
// when
eventBus.fire('directEditing.resize', {
width: 200,
height: 200,
dx: 100,
dy: 100
});
// then
var bounds = bbox(labelEditingPreview.path);
expect(bounds).to.eql({ x: 0, y: 0, width: 10, height: 300 });
}
));
it('[text annotations] should resize preview below 0', inject(
function(directEditing, elementRegistry, eventBus, labelEditingPreview) {
// given
var textAnnotation = elementRegistry.get('TextAnnotation_1');
directEditing.activate(textAnnotation);
// when
eventBus.fire('directEditing.resize', {
width: 200,
height: 200,
dx: -300,
dy: -300
});
// then
var bounds = bbox(labelEditingPreview.path);
expect(bounds).to.eql({ x: 0, y: 0, width: 10, height: 0 });
}
));
});
describe('complete/cancel', function() {
it('[external labels AND text annotations] should remove marker to hide element on complete', inject(
function(directEditing, elementRegistry) {
// given
var textAnnotation = elementRegistry.get('TextAnnotation_1');
directEditing.activate(textAnnotation);
// when
directEditing.complete();
// then
var gfx = elementRegistry.getGraphics(textAnnotation);
expect(gfx.classList.contains('djs-element-hidden')).to.be.false;
}
));
it('[internal labels] should remove marker to hide label on complete', inject(
function(directEditing, elementRegistry) {
// given
var task = elementRegistry.get('Task_1');
directEditing.activate(task);
// when
directEditing.complete();
// then
var gfx = elementRegistry.getGraphics(task);
expect(gfx.classList.contains('djs-label-hidden')).to.be.false;
}
));
});
});
function bbox(el) {
return pick(el.getBBox(), [ 'x', 'y', 'width', 'height' ]);
}
================================================
FILE: test/spec/features/label-editing/LabelEditingProviderSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import labelEditingModule from 'lib/features/label-editing';
import coreModule from 'lib/core';
import draggingModule from 'diagram-js/lib/features/dragging';
import modelingModule from 'lib/features/modeling';
import autoPlaceModule from 'lib/features/auto-place';
import {
getLabel
} from 'lib/util/LabelUtil';
import {
createCanvasEvent as canvasEvent
} from '../../../util/MockEvents';
var MEDIUM_LINE_HEIGHT = 12 * 1.2;
var DELTA = 3;
describe('features - label-editing', function() {
var diagramXML = require('./LabelEditing.bpmn');
describe('basics', function() {
beforeEach(bootstrapModeler(diagramXML, {
modules: [
labelEditingModule,
coreModule,
draggingModule,
modelingModule,
autoPlaceModule
]
}));
it('should register on dblclick', inject(
function(elementRegistry, directEditing, eventBus) {
// given
var shape = elementRegistry.get('Task_1');
// when
eventBus.fire('element.dblclick', { element: shape });
// then
expect(directEditing.isActive()).to.be.true;
// clean up
directEditing._textbox.destroy();
}
));
it('should cancel on ', inject(
function(elementRegistry, directEditing, eventBus) {
// given
var shape = elementRegistry.get('Task_1'),
task = shape.businessObject;
var oldName = task.name;
// activate
eventBus.fire('element.dblclick', { element: shape });
var textbox = directEditing._textbox.content;
// when
// change + ESC is pressed
textbox.innerText = 'new value';
triggerKeyEvent(textbox, 'keydown', 27);
// then
expect(directEditing.isActive()).to.be.false;
expect(task.name).to.equal(oldName);
}
));
it('should complete on drag start', inject(
function(elementRegistry, directEditing, dragging) {
// given
var shape = elementRegistry.get('Task_1'),
task = shape.businessObject;
directEditing.activate(shape);
directEditing._textbox.content.textContent = 'FOO BAR';
// when
dragging.init(null, { x: 0, y: 0 }, 'foo');
// then
expect(task.name).to.equal('FOO BAR');
}
));
it('should complete on auto place', inject(
function(elementRegistry, directEditing, elementFactory, autoPlace) {
// given
var shape = elementRegistry.get('Task_1'),
task = shape.businessObject;
directEditing.activate(shape);
directEditing._textbox.content.textContent = 'FOO BAR';
// when
autoPlace.append(shape, elementFactory.create(
'shape',
{ type: 'bpmn:ServiceTask' }
));
// then
expect(task.name).to.equal('FOO BAR');
}
));
it('should complete on root element click', inject(
function(elementRegistry, directEditing, canvas, eventBus) {
// given
var shape = elementRegistry.get('Task_1'),
task = shape.businessObject;
// activate
eventBus.fire('element.dblclick', { element: shape });
var newName = 'new value';
// a element
var content = directEditing._textbox.content;
content.innerText = newName;
// when
// change +
eventBus.fire('element.mousedown', { element: canvas.getRootElement() });
// then
expect(directEditing.isActive()).to.be.false;
expect(task.name).to.equal(newName);
}
));
it('should complete on root element changed', inject(
function(elementRegistry, directEditing, canvas, eventBus) {
// given
var shape = elementRegistry.get('Task_1'),
task = shape.businessObject,
newRoot = elementRegistry.get('SubProcess_2_plane');
// activate
eventBus.fire('element.dblclick', { element: shape });
var newName = 'new value';
// a element
var content = directEditing._textbox.content;
content.innerText = newName;
// when
canvas.setRootElement(newRoot);
// then
expect(directEditing.isActive()).to.be.false;
expect(task.name).to.equal(newName);
}
));
it('should complete on selection changed', inject(
function(elementRegistry, directEditing, selection) {
// given
var shape = elementRegistry.get('Task_1'),
task = shape.businessObject;
directEditing.activate(shape);
directEditing._textbox.content.textContent = 'FOO BAR';
// when
selection.select();
// then
expect(task.name).to.equal('FOO BAR');
}
));
it('should cancel on element deletion', inject(
function(elementRegistry, directEditing, modeling) {
// given
var shape = elementRegistry.get('Task_1'),
task = shape.businessObject;
directEditing.activate(shape);
directEditing._textbox.content.textContent = 'FOO BAR';
// when
modeling.removeElements([ shape ]);
// then
expect(task.name).not.to.equal('FOO BAR');
}
));
it('should cancel on selected element deletion', inject(
function(elementRegistry, directEditing, selection, modeling) {
// given
var shape = elementRegistry.get('Task_1'),
task = shape.businessObject;
selection.select(shape);
directEditing.activate(shape);
directEditing._textbox.content.textContent = 'FOO BAR';
// when
modeling.removeElements([ shape ]);
// then
expect(task.name).not.to.equal('FOO BAR');
}
));
});
describe('details', function() {
beforeEach(bootstrapModeler(diagramXML, {
modules: [
labelEditingModule,
coreModule,
modelingModule
]
}));
var create,
directEditing,
dragging,
elementFactory,
elementRegistry,
eventBus;
beforeEach(inject([
'create', 'directEditing', 'dragging',
'elementFactory', 'elementRegistry', 'eventBus',
function(_create, _directEditing, _dragging, _elementFactory, _elementRegistry, _eventBus) {
create = _create;
directEditing = _directEditing;
dragging = _dragging;
elementFactory = _elementFactory;
elementRegistry = _elementRegistry;
eventBus = _eventBus;
}
]));
function directEditActivate(element) {
if (element.waypoints) {
eventBus.fire('element.dblclick', { element: element });
} else {
eventBus.fire('element.dblclick', { element: element });
}
}
function directEditUpdate(value) {
directEditing._textbox.content.innerText = value;
}
function directEditComplete(value) {
directEditUpdate(value);
directEditing.complete();
}
function directEditCancel(value) {
directEditUpdate(value);
directEditing.cancel();
}
describe('command support', function() {
it('should update via command stack', function() {
// given
var diagramElement = elementRegistry.get('Task_1');
var listenerCalled;
eventBus.on('commandStack.changed', function(e) {
listenerCalled = true;
});
// when
directEditActivate(diagramElement);
directEditComplete('BAR');
// then
expect(listenerCalled).to.be.true;
});
it('should undo via command stack', inject(function(commandStack) {
// given
var diagramElement = elementRegistry.get('Task_1');
var oldLabel = getLabel(diagramElement);
// when
directEditActivate(diagramElement);
directEditComplete('BAR');
commandStack.undo();
// then
var label = getLabel(diagramElement);
expect(label).to.eql(oldLabel);
}));
});
describe('should unset', function() {
it('name on empty text', function() {
// given
var diagramElement = elementRegistry.get('SequenceFlow_1');
// when
directEditActivate(diagramElement);
directEditComplete(' ');
// then
expect(diagramElement.businessObject.name).not.to.exist;
});
it('text on empty text', function() {
// given
var diagramElement = elementRegistry.get('TextAnnotation_1');
// when
directEditActivate(diagramElement);
directEditComplete(' ');
// then
expect(diagramElement.businessObject.text).not.to.exist;
});
});
describe('should trigger redraw', function() {
it('on shape change', function() {
// given
var diagramElement = elementRegistry.get('Task_1');
var listenerCalled;
eventBus.on('element.changed', function(e) {
if (e.element === diagramElement) {
listenerCalled = true;
}
});
// when
directEditActivate(diagramElement);
directEditComplete('BAR');
// then
expect(listenerCalled).to.be.true;
});
it('on connection on change', function() {
// given
var diagramElement = elementRegistry.get('SequenceFlow_1');
var listenerCalled;
eventBus.on('element.changed', function(e) {
if (e.element === diagramElement.label) {
listenerCalled = true;
}
});
// when
directEditActivate(diagramElement);
directEditComplete('BAR');
// then
expect(listenerCalled).to.be.true;
});
});
describe('element support, should edit', function() {
function directEdit(elementId) {
return inject(function(elementRegistry, eventBus, directEditing) {
var diagramElement = elementRegistry.get(elementId);
var label = getLabel(diagramElement);
// when
directEditActivate(diagramElement);
// then
// expect editing to be active
expect(directEditing.getValue()).to.eql(label);
expect(directEditing.isActive()).to.be.true;
// when
directEditComplete('B');
// then
// expect update to have happened
label = getLabel(diagramElement);
expect(label).to.equal('B');
// when
directEditActivate(diagramElement);
directEditCancel('C');
// expect no label update to have happened
label = getLabel(diagramElement);
expect(label).to.equal('B');
});
}
it('task', directEdit('Task_1'));
describe('gateway', function() {
it('simple', directEdit('ExclusiveGateway_1'));
it('via label', directEdit('ExclusiveGateway_1_label'));
});
describe('event', function() {
it('start', directEdit('StartEvent_1'));
it('event via label', directEdit('StartEvent_1_label'));
it('event without label', directEdit('EndEvent_1'));
});
describe('data reference', function() {
it('data store reference', directEdit('DataStoreReference_1'));
it('data object reference', directEdit('DataObjectReference_1'));
});
describe('sequenceflow', function() {
it('simple', directEdit('SequenceFlow_1'));
it('via label', directEdit('SequenceFlow_1_label'));
it('without label', directEdit('SequenceFlow_2'));
});
describe('message flow', function() {
it('simple', directEdit('MessageFlow_1'));
it('via label', directEdit('MessageFlow_1_label'));
});
describe('pool', function() {
it('simple', directEdit('Participant_1'));
it('collapsed', directEdit('Participant_2'));
it('vertical', directEdit('Participant_3'));
it('vertical, collapsed', directEdit('Participant_4'));
});
describe('lane', function() {
it('lane with label', directEdit('Lane_1'));
it('lane without label', directEdit('Lane_2'));
});
describe('data IO', function() {
it('data input', directEdit('DataInput'));
it('data output', directEdit('DataOutput'));
});
describe('group', function() {
it('simple', directEdit('Group_1'));
it(' via label', directEdit('Group_1_label'));
});
});
describe('on element creation', function() {
function createTaskElement(context) {
var shape = elementFactory.create('shape', { type: 'bpmn:Task' }),
parent = elementRegistry.get('SubProcess_1'),
parentGfx = elementRegistry.getGraphics(parent);
create.start(canvasEvent({ x: 0, y: 0 }), [ shape ], context);
dragging.hover({
element: parent,
gfx: parentGfx
});
dragging.move(canvasEvent({ x: 400, y: 250 }));
dragging.end();
}
function createParticipant() {
var collaboration = elementRegistry.get('Collaboration_1o0amh9'),
collaborationGfx = elementRegistry.getGraphics(collaboration);
var participant = elementFactory.createParticipantShape();
// when
create.start(canvasEvent({ x: 400, y: 300 }), participant);
dragging.hover({ element: collaboration, gfx: collaborationGfx });
dragging.move(canvasEvent({ x: 400, y: 300 }));
dragging.end();
}
describe('should activate', function() {
it('on Task creation', function() {
// when
createTaskElement();
// then
expect(directEditing.isActive()).to.be.true;
});
it('on Participant creation', function() {
// when
createParticipant();
// then
expect(directEditing.isActive()).to.be.true;
});
});
it('should NOT activate with behavior hint', function() {
// when
createTaskElement({
hints: { createElementsBehavior: false }
});
// then
expect(directEditing.isActive()).to.be.false;
});
});
});
describe('group support', function() {
beforeEach(bootstrapModeler(diagramXML, {
modules: [
labelEditingModule,
coreModule,
modelingModule
],
canvas: { deferUpdate: false }
}));
it('should set label on group (no category value)', inject(
function(elementRegistry, directEditing) {
// given
var shape = elementRegistry.get('Group_2');
// when
directEditing.activate(shape);
directEditing._textbox.content.innerText = 'FOO';
directEditing.complete();
// then
var label = getLabel(shape);
expect(shape.businessObject.categoryValueRef).to.exist;
expect(label).to.equal('FOO');
}
));
});
describe('sizes', function() {
beforeEach(bootstrapModeler(diagramXML, {
modules: [
labelEditingModule,
coreModule,
modelingModule
],
canvas: { deferUpdate: false }
}));
describe('bounds', function() {
describe('external labels', function() {
it('[zoom 1] should have fixed width and element height', inject(
function(canvas, directEditing, elementRegistry) {
var zoom = 1;
canvas.zoom(zoom);
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),
y: bounds.y - (7 * zoom),
width: (90 * zoom),
height: bounds.height + (5 * zoom) + 7
});
}
));
it('[zoom 1.5] should have fixed width and element height', inject(
function(canvas, directEditing, elementRegistry) {
var zoom = 1.5;
canvas.zoom(zoom);
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),
y: bounds.y - (7 * zoom),
width: (90 * zoom),
height: bounds.height + (5 * zoom) + (7 * zoom)
});
}
));
});
describe('internal labels', function() {
it('[zoom 1] should have element size', inject(
function(canvas, directEditing, elementRegistry) {
var zoom = 1;
canvas.zoom(zoom);
var task = elementRegistry.get('Task_1');
var bounds = canvas.getAbsoluteBBox(task);
directEditing.activate(task);
expectBounds(directEditing._textbox.parent, bounds);
}
));
it('[zoom 1.5] should have element size', inject(
function(canvas, directEditing, elementRegistry) {
var zoom = 1.5;
canvas.zoom(zoom);
var task = elementRegistry.get('Task_1');
var bounds = canvas.getAbsoluteBBox(task);
directEditing.activate(task);
expectBounds(directEditing._textbox.parent, bounds);
}
));
});
describe('sequence flows', function() {
it('[zoom 1] should have fixed width and element height', inject(
function(canvas, directEditing, elementRegistry) {
var zoom = 1;
canvas.zoom(zoom);
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),
y: bounds.y - (7 * zoom),
width: (90 * zoom),
height: bounds.height + (5 * zoom) + 7
});
}
));
it('[zoom 1.5] should have fixed width and element height', inject(
function(canvas, directEditing, elementRegistry) {
var zoom = 1.5;
canvas.zoom(zoom);
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),
y: bounds.y - (7 * zoom),
width: (90 * zoom),
height: bounds.height + (5 * zoom) + (7 * zoom)
});
}
));
});
describe('text annotations', function() {
it('[zoom 1] should have element size', inject(
function(canvas, directEditing, elementRegistry) {
var zoom = 1;
canvas.zoom(zoom);
var textAnnotation = elementRegistry.get('TextAnnotation_1');
var bounds = canvas.getAbsoluteBBox(textAnnotation);
directEditing.activate(textAnnotation);
expectBounds(directEditing._textbox.parent, bounds);
}
));
it('[zoom 1.5] should have element size', inject(
function(canvas, directEditing, elementRegistry) {
var zoom = 1.5;
canvas.zoom(zoom);
var textAnnotation = elementRegistry.get('TextAnnotation_1');
var bounds = canvas.getAbsoluteBBox(textAnnotation);
directEditing.activate(textAnnotation);
expectBounds(directEditing._textbox.parent, bounds);
}
));
});
describe('expanded sub processes', function() {
it('[zoom 1] should have element width and height to fit text', inject(
function(canvas, directEditing, elementRegistry) {
var zoom = 1;
canvas.zoom(zoom);
var subProcess = elementRegistry.get('SubProcess_1');
var bounds = canvas.getAbsoluteBBox(subProcess);
directEditing.activate(subProcess);
expectBounds(directEditing._textbox.parent, {
x: bounds.x,
y: bounds.y,
width: bounds.width,
height: (MEDIUM_LINE_HEIGHT * zoom) + (7 * 2 * zoom)
});
}
));
it('[zoom 1.5] should have element width and height to fit text', inject(
function(canvas, directEditing, elementRegistry) {
var zoom = 1.5;
canvas.zoom(zoom);
var subProcess = elementRegistry.get('SubProcess_1');
var bounds = canvas.getAbsoluteBBox(subProcess);
directEditing.activate(subProcess);
expectBounds(directEditing._textbox.parent, {
x: bounds.x,
y: bounds.y,
width: bounds.width,
height: (MEDIUM_LINE_HEIGHT * zoom) + (7 * 2 * zoom)
});
}
));
});
describe('pools/lanes', function() {
it('[zoom 1] should have width of element height, height of 30', inject(
function(canvas, directEditing, elementRegistry) {
var zoom = 1;
canvas.zoom(zoom);
var pool = elementRegistry.get('Participant_1');
var bounds = canvas.getAbsoluteBBox(pool);
var mid = {
x: bounds.x + bounds.width / 2,
y: bounds.y + bounds.height / 2
};
directEditing.activate(pool);
expectBounds(directEditing._textbox.parent, {
x: bounds.x - (bounds.height / 2) + (15 * zoom),
y: mid.y - (30 * zoom) / 2,
width: bounds.height * zoom,
height: 30 * zoom
});
}
));
it('[zoom 1.5] should have width of element height, height of 30', inject(
function(canvas, directEditing, elementRegistry) {
var zoom = 1.5;
canvas.zoom(zoom);
var pool = elementRegistry.get('Participant_1');
var bounds = canvas.getAbsoluteBBox(pool);
var mid = {
x: bounds.x + bounds.width / 2,
y: bounds.y + bounds.height / 2
};
directEditing.activate(pool);
expectBounds(directEditing._textbox.parent, {
x: bounds.x - (bounds.height / 2) + (15 * zoom),
y: mid.y - (30 * zoom) / 2,
width: bounds.height,
height: 30 * zoom
});
}
));
});
describe('collapsed pools', function() {
it('[zoom 1] should have width/height of element', inject(
function(canvas, directEditing, elementRegistry) {
// given
var zoom = 1;
canvas.zoom(zoom);
var pool = elementRegistry.get('Participant_2');
var bounds = canvas.getAbsoluteBBox(pool);
// when
directEditing.activate(pool);
// then
expectBounds(directEditing._textbox.parent, {
x: bounds.x,
y: bounds.y,
width: bounds.width,
height: bounds.height
});
}
));
it('[zoom 1.5] should have width/height of element', inject(
function(canvas, directEditing, elementRegistry) {
// given
var zoom = 1.5;
canvas.zoom(zoom);
var pool = elementRegistry.get('Participant_2');
var bounds = canvas.getAbsoluteBBox(pool);
// when
directEditing.activate(pool);
// then
expectBounds(directEditing._textbox.parent, {
x: bounds.x,
y: bounds.y,
width: bounds.width,
height: bounds.height
});
}
));
});
describe('vertical pools/lanes', function() {
it('[zoom 1] should have width of element width, height of 30', inject(
function(canvas, directEditing, elementRegistry) {
// given
var zoom = 1;
canvas.zoom(zoom);
var pool = elementRegistry.get('Participant_3');
var bounds = canvas.getAbsoluteBBox(pool);
// when
directEditing.activate(pool);
// then
expectBounds(directEditing._textbox.parent, {
x: bounds.x,
y: bounds.y,
width: bounds.width,
height: 30 * zoom
});
}
));
it('[zoom 1.5] should have width of element width, height of 30', inject(
function(canvas, directEditing, elementRegistry) {
// given
var zoom = 1.5;
canvas.zoom(zoom);
var pool = elementRegistry.get('Participant_3');
var bounds = canvas.getAbsoluteBBox(pool);
// when
directEditing.activate(pool);
// then
expectBounds(directEditing._textbox.parent, {
x: bounds.x,
y: bounds.y,
width: bounds.width,
height: 30 * zoom
});
}
));
});
describe('vertical collapsed pools', function() {
it('[zoom 1] should have width/height of element', inject(
function(canvas, directEditing, elementRegistry) {
// given
var zoom = 1;
canvas.zoom(zoom);
var pool = elementRegistry.get('Participant_4');
var bounds = canvas.getAbsoluteBBox(pool);
var mid = {
x: bounds.x + bounds.width / 2,
y: bounds.y + bounds.height / 2
};
// when
directEditing.activate(pool);
// then
expectBounds(directEditing._textbox.parent, {
x: mid.x - bounds.height / 2,
y: mid.y - bounds.width / 2,
width: bounds.height,
height: bounds.width
});
}
));
it('[zoom 1.5] should have width/height of element', inject(
function(canvas, directEditing, elementRegistry) {
// given
var zoom = 1.5;
canvas.zoom(zoom);
var pool = elementRegistry.get('Participant_4');
var bounds = canvas.getAbsoluteBBox(pool);
var mid = {
x: bounds.x + bounds.width / 2,
y: bounds.y + bounds.height / 2
};
// when
directEditing.activate(pool);
// then
expectBounds(directEditing._textbox.parent, {
x: mid.x - bounds.height / 2,
y: mid.y - bounds.width / 2,
width: bounds.height,
height: bounds.width
});
}
));
});
});
});
});
// helpers //////////////////
function triggerKeyEvent(element, event, code) {
var e = document.createEvent('Events');
if (e.initEvent) {
e.initEvent(event, true, true);
}
e.keyCode = code;
e.which = code;
return element.dispatchEvent(e);
}
function expectBounds(parent, bounds) {
expect(parent.offsetLeft).to.be.closeTo(bounds.x, DELTA);
expect(parent.offsetTop).to.be.closeTo(bounds.y, DELTA);
expect(parent.offsetWidth).to.be.closeTo(bounds.width, DELTA);
expect(parent.offsetHeight).to.be.closeTo(bounds.height, DELTA);
}
================================================
FILE: test/spec/features/label-link/LabelLink.bpmn
================================================
Sequence
Sequence
Flow_1n4vntt
Sequence_Curved
Flow_08zlypo
Flow_1n4vntt
Sequence_Curved
Flow_08zlypo
================================================
FILE: test/spec/features/label-link/LabelLinkSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import coreModule from 'lib/core';
import modelingModule from 'lib/features/modeling';
import outlineModule from 'lib/features/outline';
import drilldownModule from 'lib/features/drilldown';
import labelLinkModule from 'lib/features/label-link';
import { queryAll as domQueryAll } from 'min-dom';
import { expectSvgPath } from '../../../util/svgHelpers';
describe('features/label-link - label link', function() {
var diagramXML = require('./LabelLink.bpmn');
var testModules = [
coreModule,
modelingModule,
outlineModule,
drilldownModule,
labelLinkModule
];
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
it('should not show when nothing selected', inject(function() {
var links = queryAllLinks();
expect(links).to.have.length(0);
}));
it('should show for event', inject(
function(selection, elementRegistry) {
// given
const element = elementRegistry.get('End_Event');
// when
selection.select(element);
// then
expectLinkWithPath('M450,335L450,383');
})
);
it('should show for sequence flow', inject(
function(selection, elementRegistry) {
// given
const element = elementRegistry.get('Sequence_Curved');
// when
selection.select(element);
// then
expectLinkWithPath('M328,310L364,267');
})
);
it('should show for gateway', inject(
function(selection, elementRegistry) {
// given
const element = elementRegistry.get('Gateway');
// when
selection.select(element);
// then
expectLinkWithPath('M318,185L345,147');
})
);
it('should not show if close to element', inject(
function(selection, elementRegistry) {
// given
const element = elementRegistry.get('Start_Event');
// when
selection.select(element);
// then
const links = queryAllLinks();
expect(links).to.have.length(0);
})
);
it('should update if element moved', inject(
function(selection, elementRegistry, modeling) {
// given
const element = elementRegistry.get('End_Event');
// when
selection.select(element);
modeling.moveShape(element, { x: 100, y: 0 });
// then
const links = queryAllLinks();
expect(links).to.have.length(1);
expectLinkWithPath('M532,325L459,383');
})
);
it('should update if label moved', inject(
function(selection, elementRegistry, modeling) {
// given
const element = elementRegistry.get('End_Event');
const label = element.labels[0];
// when
selection.select(label);
modeling.moveShape(label, { x: 100, y: 0 });
// then
const links = queryAllLinks();
expect(links).to.have.length(1);
expectLinkWithPath('M464,321L533,376');
})
);
it('should show when both label and target selected', inject(
function(selection, elementRegistry) {
// given
const element = elementRegistry.get('End_Event');
const label = element.labels[0];
// when
selection.select([ element, label ]);
// then
expectLinkWithPath('M450,335L450,376');
})
);
it('should show label for event in expanded subprocess', inject(
function(selection, elementRegistry) {
// given
const element = elementRegistry.get('Subprocess_Event');
// when
selection.select(element);
// then
expectLinkWithPath('M498,169L527,97');
})
);
it('should show label for event in collapsed subprocess plane', inject(
function(selection, elementRegistry, bpmnReplace, canvas) {
// given
const subprocess = elementRegistry.get('Subprocess');
// when
selection.select(subprocess);
bpmnReplace.replaceElement(subprocess, {
type: 'bpmn:SubProcess',
isExpanded: false
});
var subprocessRoot = canvas.findRoot('Subprocess_plane');
canvas.setRootElement(subprocessRoot);
selection.select(elementRegistry.get('Subprocess_Event'));
// then
expectLinkWithPath('M206,246L235,174');
})
);
it('should not show label after collapsing a subprocess', inject(
function(selection, elementRegistry, bpmnReplace) {
// given
const subprocess = elementRegistry.get('Subprocess');
// when
selection.select(subprocess);
bpmnReplace.replaceElement(subprocess, {
type: 'bpmn:SubProcess',
isExpanded: false
});
// then
const links = queryAllLinks();
expect(links).to.have.length(0);
})
);
it('should not show label after expanding a subprocess', inject(
function(selection, elementRegistry, bpmnReplace) {
// given
const subprocess = elementRegistry.get('Subprocess');
// when
selection.select(subprocess);
bpmnReplace.replaceElement(subprocess, {
type: 'bpmn:SubProcess',
isExpanded: false
});
bpmnReplace.replaceElement(subprocess, {
type: 'bpmn:SubProcess',
isExpanded: true
});
// then
const links = queryAllLinks();
expect(links).to.have.length(0);
})
);
});
// helpers
function queryAllLinks() {
return domQueryAll('.bjs-label-link');
}
function expectLinkWithPath(path) {
const links = queryAllLinks();
const linePath = links[0].getAttribute('d');
expectSvgPath(linePath, path);
}
================================================
FILE: test/spec/features/modeling/AppendShapeSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import {
find
} from 'min-dash';
import {
getDi,
is,
getBusinessObject
} from 'lib/util/ModelUtil';
import modelingModule from 'lib/features/modeling';
import coreModule from 'lib/core';
describe('features/modeling - append shape', function() {
var diagramXML = require('../../../fixtures/bpmn/simple.bpmn');
var testModules = [ coreModule, modelingModule ];
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
describe('shape handling', function() {
it('should execute', inject(function(elementRegistry, modeling) {
// given
var startEventShape = elementRegistry.get('StartEvent_1');
// when
var targetShape = modeling.appendShape(startEventShape, { type: 'bpmn:Task' }),
target = targetShape.businessObject;
// then
expect(targetShape).to.exist;
expect(target.$instanceOf('bpmn:Task')).to.be.true;
}));
it('should create DI', inject(function(elementRegistry, modeling) {
// given
var startEventShape = elementRegistry.get('StartEvent_1');
var startEventDi = getDi(startEventShape);
// when
var targetShape = modeling.appendShape(startEventShape, { type: 'bpmn:Task' }),
targetDi = getDi(targetShape);
// then
expect(targetDi).to.exist;
expect(targetDi.$parent).to.eql(startEventDi.$parent);
expect(targetDi).to.have.bounds(targetShape);
}));
it('should add to parent (sub process)', inject(function(elementRegistry, modeling) {
// given
var startEventShape = elementRegistry.get('StartEvent_1');
var subProcessShape = elementRegistry.get('SubProcess_1');
var subProcess = subProcessShape.businessObject;
// when
var targetShape = modeling.appendShape(startEventShape, { type: 'bpmn:Task' }),
target = targetShape.businessObject;
// then
expect(subProcess.get('flowElements')).to.include(target);
}));
it('should add connection + DI', inject(function(elementRegistry, modeling) {
// given
var startEventShape = elementRegistry.get('StartEvent_1');
var subProcessShape = elementRegistry.get('SubProcess_1');
var startEventBo = startEventShape.businessObject,
subProcessBo = subProcessShape.businessObject;
// when
var targetShape = modeling.appendShape(startEventShape, { type: 'bpmn:Task' }),
targetBo = targetShape.businessObject;
var connection = targetShape.incoming[0],
connectionDi = getDi(connection),
connectionBo = getBusinessObject(connection);
// then
expect(connection).to.exist;
expect(is(connection, 'bpmn:SequenceFlow')).to.be.true;
expect(connectionBo.sourceRef).to.eql(startEventBo);
expect(connectionBo.targetRef).to.eql(targetBo);
expect(connectionBo.$parent).to.equal(subProcessBo);
// https://github.com/bpmn-io/bpmn-js/issues/1544
expect(connectionDi.waypoints).not.to.exist;
expect(connectionDi.waypoint).to.have.length(2);
}));
});
describe('undo support', function() {
it('should undo add to parent', inject(function(elementRegistry, modeling, commandStack) {
// given
var startEventShape = elementRegistry.get('StartEvent_1'),
subProcessShape = elementRegistry.get('SubProcess_1');
var subProcess = subProcessShape.businessObject,
subProcessDi = getDi(subProcessShape);
var targetShape = modeling.appendShape(startEventShape, { type: 'bpmn:Task' }),
target = targetShape.businessObject,
targetDi = getDi(targetShape);
// when
commandStack.undo();
// then
expect(subProcess.get('flowElements')).not.to.include(target);
expect(subProcessDi.$parent.get('planeElement')).not.to.include(targetDi);
}));
it('should undo add shape label', inject(function(elementRegistry, modeling, commandStack) {
// given
var startEventShape = elementRegistry.get('StartEvent_1'),
subProcessShape = elementRegistry.get('SubProcess_1');
var startEvent = startEventShape.businessObject,
startEventDi = getDi(startEventShape),
subProcess = subProcessShape.businessObject;
var targetShape = modeling.appendShape(startEventShape, { type: 'bpmn:EndEvent' }),
target = targetShape.businessObject;
var connection = find(subProcess.get('flowElements'), function(e) {
return e.sourceRef === startEvent && e.targetRef === target;
}),
connectionDi = getDi(elementRegistry.get(connection.id));
// assume
expect(connectionDi).to.exist;
// when
commandStack.undo();
// then
expect(connection.sourceRef).to.be.null;
expect(connection.targetRef).to.be.null;
expect(connection.$parent).to.be.null;
expect(startEventDi.$parent.get('planeElement')).not.to.include(connectionDi);
expect(targetShape.label).not.to.exist;
expect(elementRegistry.get(target.id + '_label')).not.to.exist;
}));
it('should undo add connection', inject(function(elementRegistry, modeling, commandStack) {
// given
var startEventShape = elementRegistry.get('StartEvent_1');
var subProcessShape = elementRegistry.get('SubProcess_1');
var startEvent = startEventShape.businessObject,
subProcess = subProcessShape.businessObject,
subProcessDi = getDi(subProcessShape);
var targetShape = modeling.appendShape(startEventShape, { type: 'bpmn:Task' }),
target = targetShape.businessObject;
var connection = find(subProcess.get('flowElements'), function(e) {
return e.sourceRef === startEvent && e.targetRef === target;
}),
connectionDi = getDi(elementRegistry.get(connection.id));
// assume
expect(connectionDi).to.exist;
// when
commandStack.undo();
// then
expect(connection.sourceRef).to.be.null;
expect(connection.targetRef).to.be.null;
expect(startEvent.get('outgoing')).not.to.include(connection);
expect(target.get('incoming')).not.to.include(connection);
expect(connection.$parent).to.be.null;
expect(subProcessDi.$parent.get('planeElement')).not.to.include(connectionDi);
expect(elementRegistry.get(targetShape.id)).not.to.exist;
}));
it('should undo add connection label', inject(function(elementRegistry, modeling, commandStack) {
// given
var startEventShape = elementRegistry.get('StartEvent_1');
var subProcessShape = elementRegistry.get('SubProcess_1');
var startEvent = startEventShape.businessObject,
subProcess = subProcessShape.businessObject,
subProcessDi = getDi(subProcessShape);
var targetShape = modeling.appendShape(startEventShape, { type: 'bpmn:Task' }),
target = targetShape.businessObject;
var connection = find(subProcess.get('flowElements'), function(e) {
return e.sourceRef === startEvent && e.targetRef === target;
}),
connectionDi = getDi(elementRegistry.get(connection.id));
// assume
expect(connectionDi).to.exist;
// when
commandStack.undo();
// then
expect(connection.sourceRef).to.be.null;
expect(connection.targetRef).to.be.null;
expect(connection.$parent).to.be.null;
expect(subProcessDi.$parent.get('planeElement')).not.to.include(connectionDi);
expect(elementRegistry.get(connection.id + '_label')).not.to.exist;
}));
it('should redo appending multiple shapes', inject(function(elementRegistry, modeling, commandStack) {
// given
var startEventShape = elementRegistry.get('StartEvent_1');
var subProcessShape = elementRegistry.get('SubProcess_1');
var targetShape = modeling.appendShape(startEventShape, { type: 'bpmn:Task' });
var targetShape2 = modeling.appendShape(targetShape, { type: 'bpmn:UserTask' });
// when
commandStack.undo();
commandStack.undo();
commandStack.redo();
commandStack.redo();
// then
// expect redo to work on original target object
expect(targetShape.parent).to.eql(subProcessShape);
// when
commandStack.undo();
commandStack.undo();
// then
expect(targetShape2.parent).to.be.null;
expect(elementRegistry.get(targetShape2.id)).not.to.exist;
}));
it('should redo add connection', inject(function(elementRegistry, modeling, commandStack) {
// given
var startEventShape = elementRegistry.get('StartEvent_1');
var subProcessShape = elementRegistry.get('SubProcess_1');
var startEvent = startEventShape.businessObject,
subProcess = subProcessShape.businessObject,
subProcessDi = getDi(subProcessShape);
var targetShape = modeling.appendShape(startEventShape, { type: 'bpmn:Task' }),
target = targetShape.businessObject;
var connection = find(subProcess.get('flowElements'), function(e) {
return e.sourceRef === startEvent && e.targetRef === target;
}),
connectionDi = getDi(elementRegistry.get(connection.id));
// assume
expect(connectionDi).to.exist;
// when
commandStack.undo();
commandStack.redo();
commandStack.undo();
// then
expect(connection.sourceRef).to.be.null;
expect(connection.targetRef).to.be.null;
expect(connection.$parent).to.be.null;
expect(subProcessDi.$parent.get('planeElement')).not.to.include(connectionDi);
}));
});
describe('bpmn element support', function() {
describe('ExclusiveGateway', function() {
it('should append', inject(function(elementRegistry, modeling) {
// given
var startEventShape = elementRegistry.get('StartEvent_1');
// when
var targetShape = modeling.appendShape(startEventShape, { type: 'bpmn:ExclusiveGateway' }),
target = targetShape.businessObject;
// then
expect(targetShape).to.exist;
expect(target.$instanceOf('bpmn:ExclusiveGateway')).to.be.true;
}));
it('should add to parent (sub process)', inject(function(elementRegistry, modeling) {
// given
var startEventShape = elementRegistry.get('StartEvent_1');
var subProcessShape = elementRegistry.get('SubProcess_1');
var subProcess = subProcessShape.businessObject;
// when
var targetShape = modeling.appendShape(startEventShape, { type: 'bpmn:ExclusiveGateway' }),
target = targetShape.businessObject;
// then
expect(subProcess.get('flowElements')).to.include(target);
}));
it('should undo append', inject(function(elementRegistry, modeling, commandStack) {
// given
var startEventShape = elementRegistry.get('StartEvent_1');
var subProcessShape = elementRegistry.get('SubProcess_1');
var subProcess = subProcessShape.businessObject,
subProcessDi = getDi(subProcessShape);
var targetShape = modeling.appendShape(startEventShape, { type: 'bpmn:ExclusiveGateway' }),
target = targetShape.businessObject,
targetDi = getDi(targetShape);
// when
commandStack.undo();
// then
expect(subProcess.get('flowElements')).not.to.include(target);
expect(subProcessDi.$parent.get('planeElement')).not.to.include(targetDi);
}));
});
});
});
================================================
FILE: test/spec/features/modeling/BendpointsSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import modelingModule from 'lib/features/modeling';
import bendpointsModule from 'diagram-js/lib/features/bendpoints';
import coreModule from 'lib/core';
describe('features/bendpoints', function() {
var diagramXML = require('../../../fixtures/bpmn/features/drop/drop.bpmn');
var testModules = [ coreModule, bendpointsModule, modelingModule ];
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
it('should contain bendpoints', inject(function(bendpoints) {
expect(bendpoints).to.exist;
}));
});
================================================
FILE: test/spec/features/modeling/BpmnFactorySpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import modelingModule from 'lib/features/modeling';
import coreModule from 'lib/core';
describe('features - bpmn-factory', function() {
var diagramXML = require('../../../fixtures/bpmn/simple.bpmn');
var testModules = [ modelingModule, coreModule ];
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
describe('create element', function() {
it('should return instance', inject(function(bpmnFactory) {
var task = bpmnFactory.create('bpmn:Task');
expect(task).to.exist;
expect(task.$type).to.equal('bpmn:Task');
}));
it('should assign id (with semantic prefix)', inject(function(bpmnFactory) {
var plane = bpmnFactory.create('bpmndi:BPMNPlane');
expect(plane.$type).to.equal('bpmndi:BPMNPlane');
expect(plane.id).to.match(/^BPMNPlane_/g);
}));
it('should assign bpmn:LaneSet id', inject(function(bpmnFactory) {
var set = bpmnFactory.create('bpmn:LaneSet');
expect(set.id).to.exist;
}));
describe('generic id', function() {
it('should assign id with generic semantic prefix (Activity)', inject(function(bpmnFactory) {
var task = bpmnFactory.create('bpmn:ServiceTask');
expect(task.$type).to.equal('bpmn:ServiceTask');
expect(task.id).to.match(/^Activity_/g);
}));
it('should assign id with generic semantic prefix (Gateway)', inject(function(bpmnFactory) {
var gateway = bpmnFactory.create('bpmn:ParallelGateway');
expect(gateway.$type).to.equal('bpmn:ParallelGateway');
expect(gateway.id).to.match(/^Gateway_/g);
}));
it('should assign id with generic semantic prefix (Event)', inject(function(bpmnFactory) {
var event = bpmnFactory.create('bpmn:EndEvent');
expect(event.$type).to.equal('bpmn:EndEvent');
expect(event.id).to.match(/^Event_/g);
}));
it('should assign id with generic semantic prefix (SequenceFlow)', inject(
function(bpmnFactory) {
var flow = bpmnFactory.create('bpmn:SequenceFlow');
expect(flow.$type).to.equal('bpmn:SequenceFlow');
expect(flow.id).to.match(/^Flow_/g);
})
);
it('should assign id with generic semantic prefix (MessageFlow)', inject(
function(bpmnFactory) {
var flow = bpmnFactory.create('bpmn:MessageFlow');
expect(flow.$type).to.equal('bpmn:MessageFlow');
expect(flow.id).to.match(/^Flow_/g);
})
);
it('should assign id with specific semantic prefix (DataStore)', inject(
function(bpmnFactory) {
var dataStore = bpmnFactory.create('bpmn:DataStore');
expect(dataStore.$type).to.equal('bpmn:DataStore');
expect(dataStore.id).to.match(/^DataStore_/g);
})
);
it('should assign id with specific semantic prefix (DataObject)', inject(
function(bpmnFactory) {
var dataObject = bpmnFactory.create('bpmn:DataObject');
expect(dataObject.$type).to.equal('bpmn:DataObject');
expect(dataObject.id).to.match(/^DataObject_/g);
})
);
it('should assign id with specific semantic prefix (DataObjectReference)', inject(
function(bpmnFactory) {
var dataObjectReference = bpmnFactory.create('bpmn:DataObjectReference');
expect(dataObjectReference.$type).to.equal('bpmn:DataObjectReference');
expect(dataObjectReference.id).to.match(/^DataObjectReference_/g);
})
);
});
it('should claim provided id', inject(function(bpmnFactory, moddle) {
var task = bpmnFactory.create('bpmn:Task', { id: 'foo' });
expect(task).to.exist;
expect(task.id).to.eql('foo');
expect(moddle.ids.assigned('foo')).to.exist;
}));
});
describe('create di', function() {
it('should create waypoints', inject(function(bpmnFactory) {
// given
var waypoints = [
{ original: { x: 0, y: 0 }, x: 0, y: 0 },
{ original: { x: 0, y: 0 }, x: 0, y: 0 }
];
// when
var result = bpmnFactory.createDiWaypoints(waypoints);
// then
expect(result).eql([
bpmnFactory.create('dc:Point', { x: 0, y: 0 }),
bpmnFactory.create('dc:Point', { x: 0, y: 0 })
]);
// expect original not to have been accidently serialized
expect(result[0].$attrs).to.eql({});
}));
});
});
================================================
FILE: test/spec/features/modeling/BpmnUpdater.bpmn
================================================
SequenceFlow_1
SequenceFlow_2
SequenceFlow_1
SequenceFlow_2
SequenceFlow_3
SequenceFlow_3
SequenceFlow_4
SequenceFlow_4
SequenceFlow_5
SequenceFlow_5
================================================
FILE: test/spec/features/modeling/BpmnUpdater.incompleteDi.bpmn
================================================
================================================
FILE: test/spec/features/modeling/BpmnUpdaterSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import { getDi } from 'lib/util/ModelUtil';
import { pick } from 'min-dash';
import coreModule from 'lib/core';
import modelingModule from 'lib/features/modeling';
var testModules = [ coreModule, modelingModule ];
describe('features - bpmn-updater', function() {
describe('connection di', function() {
var diagramXML = require('./BpmnUpdater.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
it('should update after deleting intermediate element', inject(
function(modeling, elementRegistry) {
// given
// sequence flow with existing sourceElement and targetElement di information
var task = elementRegistry.get('Task_1'),
sequenceFlowDi = getDi(elementRegistry.get('SequenceFlow_1')),
startEventDi = getDi(elementRegistry.get('StartEvent_1')),
endEventDi = getDi(elementRegistry.get('EndEvent_1'));
// when
modeling.removeElements([ task ]);
// then
expect(sequenceFlowDi.sourceElement).to.equal(startEventDi);
expect(sequenceFlowDi.targetElement).to.equal(endEventDi);
}
));
it('should update on drop on flow', inject(
function(modeling, elementRegistry, elementFactory) {
// given
// sequence flow with existing sourceElement and targetElement di information
var sequenceFlow = elementRegistry.get('SequenceFlow_3'),
startEventDi = getDi(elementRegistry.get('StartEvent_2')),
sequenceFlowDi = getDi(sequenceFlow);
var intermediateThrowEvent = elementFactory.createShape({
type: 'bpmn:IntermediateThrowEvent'
});
var dropPosition = { x: 320, y: 260 };
// when
var event = modeling.createShape(intermediateThrowEvent, dropPosition, sequenceFlow);
// then
expect(sequenceFlowDi.sourceElement).to.equal(startEventDi);
expect(sequenceFlowDi.targetElement).to.equal(getDi(event));
}
));
it('should not create new di refs', inject(
function(modeling, elementRegistry, elementFactory) {
// given
// sequence flow without any sourceElement and targetElement di information
var sequenceFlow = elementRegistry.get('SequenceFlow_4'),
sequenceFlowDi = getDi(sequenceFlow);
var intermediateThrowEvent = elementFactory.createShape({
type: 'bpmn:IntermediateThrowEvent'
});
var dropPosition = { x: 320, y: 260 };
// when
modeling.createShape(intermediateThrowEvent, dropPosition, sequenceFlow);
// then
expect(sequenceFlowDi.sourceElement).not.to.exist;
expect(sequenceFlowDi.targetElement).not.to.exist;
}
));
});
describe('connection cropping', function() {
var diagramXML = require('./BpmnUpdater.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
afterEach(sinon.restore);
it('should crop connection only once per reconnect', inject(
function(modeling, elementRegistry, connectionDocking) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow_1'),
target = elementRegistry.get('EndEvent_2'),
cropSpy = sinon.spy(connectionDocking, 'getCroppedWaypoints');
// when
modeling.reconnectEnd(sequenceFlow, target, { x: 418, y: 260 });
// then
expect(cropSpy).to.have.been.calledOnce;
expect(cropSpy).to.have.been.calledWith(sequenceFlow);
}
));
it('should not crop connection after pasting', inject(
function(canvas, copyPaste, elementRegistry, connectionDocking) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow_5'),
target = elementRegistry.get('Task_2'),
cropSpy = sinon.spy(connectionDocking, 'getCroppedWaypoints');
copyPaste.copy([
target,
sequenceFlow
]);
// when
copyPaste.paste({
element: canvas.getRootElement(),
point: {
x: 500,
y: 500
}
});
// then
expect(cropSpy).not.to.have.been.calledOnce;
}
));
});
describe('incomplete DI', function() {
var diagramXML = require('./BpmnUpdater.incompleteDi.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
it('should add missing label bpmndi:Bounds', inject(
function(modeling, elementRegistry) {
// given
var event = elementRegistry.get('StartEvent'),
label = event.label,
di = getDi(event);
// when
modeling.moveElements([ label ], { x: 20, y: 20 });
var labelBounds = di.label.bounds;
// then
expect(labelBounds).to.exist;
expect(labelBounds).to.include.keys(
'x', 'y',
'width', 'height'
);
}
));
it('should add missing bpmndi:BPMNLabel', inject(
function(modeling, elementRegistry) {
// given
var event = elementRegistry.get('StartEvent_2'),
label = event.label,
di = getDi(event);
// when
modeling.moveElements([ label ], { x: 20, y: 20 });
var diLabel = di.label;
// then
expect(diLabel).to.exist;
expect(diLabel.bounds).to.exist;
}
));
});
describe('update embedded label bounds', function() {
var diagramXML = require('./BpmnUpdater.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
var bounds,
di;
beforeEach(inject(function(elementRegistry, modeling) {
// given
var task = elementRegistry.get('Task_3');
di = getDi(task);
bounds = pick(di.get('label').get('bounds'), [ 'x', 'y', 'width', 'height' ]);
// when
modeling.moveShape(task, {
x: 100,
y: 100
});
}));
it('', function() {
// then
expect(di.get('label').get('bounds')).to.include({
x: bounds.x + 100,
y: bounds.y + 100,
width: bounds.width,
height: bounds.height
});
});
it('', inject(function(commandStack) {
// when
commandStack.undo();
// then
expect(di.get('label').get('bounds')).to.include({
x: bounds.x,
y: bounds.y,
width: bounds.width,
height: bounds.height
});
}));
it('', inject(function(commandStack) {
// when
commandStack.undo();
commandStack.redo();
// then
expect(di.get('label').get('bounds')).to.include({
x: bounds.x + 100,
y: bounds.y + 100,
width: bounds.width,
height: bounds.height
});
}));
});
describe('BPMNLabel', function() {
describe('embedded', function() {
it('should set BPMNLabel on task', inject(function(modeling, elementRegistry) {
// given
var task = elementRegistry.get('Task_1');
// when
modeling.updateLabel(task, 'foo');
// then
expect(task.businessObject.name).to.equal('foo');
expect(getDi(task).label).to.exist;
}));
it('should unset BPMNLabel on task', inject(function(modeling, elementRegistry) {
// given
var task = elementRegistry.get('Task_3');
// when
modeling.updateLabel(task, '');
// then
expect(task.businessObject.name).to.equal('');
expect(getDi(task)).not.to.have.property('label');
}));
});
});
});
================================================
FILE: test/spec/features/modeling/CreateConnectionSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import {
getDi
} from 'lib/util/ModelUtil';
import modelingModule from 'lib/features/modeling';
import coreModule from 'lib/core';
describe('features/modeling - create connection', function() {
var diagramXML = require('../../../fixtures/bpmn/sequence-flows.bpmn');
var testModules = [ coreModule, modelingModule ];
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
it('should connect', inject(function(elementRegistry, modeling, bpmnFactory) {
// given
var taskShape = elementRegistry.get('Task_1'),
task = taskShape.businessObject,
taskDi = getDi(taskShape),
gatewayShape = elementRegistry.get('Gateway_1'),
gateway = gatewayShape.businessObject;
// when
var sequenceFlowConnection = modeling.createConnection(taskShape, gatewayShape, {
type: 'bpmn:SequenceFlow'
}, taskShape.parent);
var sequenceFlow = sequenceFlowConnection.businessObject,
sequenceFlowDi = getDi(sequenceFlowConnection);
// then
expect(sequenceFlowConnection).to.exist;
expect(sequenceFlow).to.exist;
expect(sequenceFlow.sourceRef).to.eql(task);
expect(sequenceFlow.targetRef).to.eql(gateway);
expect(task.outgoing).to.include(sequenceFlow);
expect(gateway.incoming).to.include(sequenceFlow);
expect(sequenceFlowDi.$parent).to.eql(taskDi.$parent);
expect(sequenceFlowDi.$parent.planeElement).to.include(sequenceFlowDi);
// expect cropped connection
expect(sequenceFlowConnection.waypoints).eql([
{ original: { x: 242, y: 376 }, x: 292, y: 376 },
{ x: 410, y: 376 },
{ x: 410, y: 341 },
{ original: { x: 553, y: 341 }, x: 528, y: 341 }
]);
var diWaypoints = bpmnFactory.createDiWaypoints([
{ x: 292, y: 376 },
{ x: 410, y: 376 },
{ x: 410, y: 341 },
{ x: 528, y: 341 }
]);
// expect cropped waypoints in di
expect(sequenceFlowDi.waypoint).eql(diWaypoints);
}));
it('should connect with custom start / end', inject(function(elementRegistry, modeling) {
// given
var sourceShape = elementRegistry.get('Task_2'),
sourcePosition = {
x: 740,
y: 400
},
targetShape = elementRegistry.get('Task_3'),
targetPosition = {
x: 420,
y: 130
};
// when
var newConnection = modeling.connect(
sourceShape, targetShape,
null,
{
connectionStart: sourcePosition,
connectionEnd: targetPosition
}
);
// then
// expect cropped connection with custom start/end
expect(newConnection).to.have.waypoints([
{ x: 734, y: 400 },
{ x: 590, y: 400 },
{ x: 590, y: 130 },
{ x: 447, y: 130 }
]);
}));
it('should undo', inject(function(elementRegistry, commandStack, modeling) {
// given
var taskShape = elementRegistry.get('Task_1'),
task = taskShape.businessObject,
gatewayShape = elementRegistry.get('Gateway_1'),
gateway = gatewayShape.businessObject;
var sequenceFlowConnection = modeling.createConnection(taskShape, gatewayShape, {
type: 'bpmn:SequenceFlow'
}, taskShape.parent);
var sequenceFlow = sequenceFlowConnection.businessObject;
// when
commandStack.undo();
// then
expect(sequenceFlow.$parent).to.be.null;
expect(sequenceFlow.sourceRef).to.be.null;
expect(sequenceFlow.targetRef).to.be.null;
expect(task.outgoing).not.to.include(sequenceFlow);
expect(gateway.incoming).not.to.include(sequenceFlow);
}));
it('should redo', inject(function(elementRegistry, commandStack, modeling) {
// given
var taskShape = elementRegistry.get('Task_1'),
task = taskShape.businessObject,
taskDi = getDi(taskShape),
gatewayShape = elementRegistry.get('Gateway_1'),
gateway = gatewayShape.businessObject;
var sequenceFlowConnection = modeling.createConnection(taskShape, gatewayShape, {
type: 'bpmn:SequenceFlow'
}, taskShape.parent);
var sequenceFlow = sequenceFlowConnection.businessObject,
sequenceFlowDi = getDi(sequenceFlowConnection);
var newWaypoints = sequenceFlowConnection.waypoints,
newDiWaypoints = sequenceFlowDi.waypoint;
// when
commandStack.undo();
commandStack.redo();
// then
expect(sequenceFlow.sourceRef).to.eql(task);
expect(sequenceFlow.targetRef).to.eql(gateway);
expect(task.outgoing).to.include(sequenceFlow);
expect(gateway.incoming).to.include(sequenceFlow);
expect(sequenceFlowDi.$parent).to.eql(taskDi.$parent);
expect(sequenceFlowDi.$parent.planeElement).to.include(sequenceFlowDi);
// expect cropped connection
expect(sequenceFlowConnection.waypoints).eql(newWaypoints);
// expect cropped waypoints in di
expect(sequenceFlowDi.waypoint).eql(newDiWaypoints);
}));
});
================================================
FILE: test/spec/features/modeling/DeleteConnectionSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import modelingModule from 'lib/features/modeling';
import coreModule from 'lib/core';
describe('features/modeling - #removeConnection', function() {
var diagramXML = require('../../../fixtures/bpmn/sequence-flows.bpmn');
var testModules = [ coreModule, modelingModule ];
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
describe('shape handling', function() {
it('should execute', inject(function(elementRegistry, modeling) {
// given
var sequenceFlowShape = elementRegistry.get('SequenceFlow_2'),
sequenceFlow = sequenceFlowShape.businessObject;
// when
modeling.removeConnection(sequenceFlowShape);
// then
expect(sequenceFlow.$parent).to.be.null;
}));
});
describe('undo support', function() {
it('should undo', inject(function(elementRegistry, modeling, commandStack) {
// given
var sequenceFlowShape = elementRegistry.get('SequenceFlow_2'),
sequenceFlow = sequenceFlowShape.businessObject,
parent = sequenceFlow.$parent;
// when
modeling.removeConnection(sequenceFlowShape);
commandStack.undo();
// then
expect(sequenceFlow.$parent).to.eql(parent);
}));
});
describe('redo support', function() {
it('redo', inject(function(elementRegistry, modeling, commandStack) {
// given
var sequenceFlowShape = elementRegistry.get('SequenceFlow_2'),
sequenceFlow = sequenceFlowShape.businessObject;
// when
modeling.removeConnection(sequenceFlowShape);
commandStack.undo();
commandStack.redo();
// then
expect(sequenceFlow.$parent).to.be.null;
}));
});
});
================================================
FILE: test/spec/features/modeling/DeleteParticipantSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import { getDi } from 'lib/util/ModelUtil';
import modelingModule from 'lib/features/modeling';
import coreModule from 'lib/core';
describe('features/modeling - delete participant', function() {
var testModules = [ coreModule, modelingModule ];
describe('last remaining', function() {
describe('should transform diagram into process diagram', function() {
var processDiagramXML = require('../../../fixtures/bpmn/collaboration/collaboration-empty-participant.bpmn');
beforeEach(bootstrapModeler(processDiagramXML, { modules: testModules }));
it('execute', inject(function(modeling, elementRegistry, canvas) {
// given
var participantShape = elementRegistry.get('_Participant_2'),
participant = participantShape.businessObject,
participantDi = getDi(participantShape),
process = participant.processRef,
collaborationElement = participantShape.parent,
collaboration = collaborationElement.businessObject,
diPlane = getDi(collaborationElement),
bpmnDefinitions = collaboration.$parent;
// when
modeling.removeShape(participantShape);
// then
expect(participant.$parent).not.to.be.ok;
var newRootShape = canvas.getRootElement(),
newRootBusinessObject = newRootShape.businessObject;
expect(newRootBusinessObject.$instanceOf('bpmn:Process')).to.be.true;
// collaboration DI is unwired
expect(participantDi.$parent).not.to.be.ok;
expect(collaborationElement.di).not.to.be.ok;
expect(bpmnDefinitions.rootElements).not.to.include(process);
expect(bpmnDefinitions.rootElements).not.to.include(collaboration);
// process DI is wired
expect(diPlane.bpmnElement).to.eql(newRootBusinessObject);
expect(newRootShape.di).to.eql(diPlane);
expect(bpmnDefinitions.rootElements).to.include(newRootBusinessObject);
}));
it('undo', inject(function(modeling, elementRegistry, canvas, commandStack) {
// given
var participantShape = elementRegistry.get('_Participant_2'),
participant = participantShape.businessObject,
originalRootElement = participantShape.parent,
originalRootElementBo = originalRootElement.businessObject,
bpmnDefinitions = originalRootElementBo.$parent,
participantDi = getDi(participantShape),
diPlane = participantDi.$parent;
modeling.removeShape(participantShape);
// when
commandStack.undo();
// then
expect(participant.$parent).to.eql(originalRootElementBo);
expect(originalRootElementBo.$parent).to.eql(bpmnDefinitions);
expect(canvas.getRootElement()).to.eql(originalRootElement);
// di is unwired
expect(participantDi.$parent).to.eql(getDi(originalRootElement));
// new di is wired
expect(diPlane.bpmnElement).to.eql(originalRootElementBo);
}));
});
});
});
================================================
FILE: test/spec/features/modeling/DeleteShape.cropping.bpmn
================================================
SequenceFlow_1
SequenceFlow_2
SequenceFlow_1
SequenceFlow_2
================================================
FILE: test/spec/features/modeling/DeleteShapeSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import modelingModule from 'lib/features/modeling';
import coreModule from 'lib/core';
var testModules = [ coreModule, modelingModule ];
describe('features/modeling - #removeShape', function() {
var diagramXML = require('../../../fixtures/bpmn/sequence-flows.bpmn');
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
describe('shape handling', function() {
it('should execute', inject(function(elementRegistry, modeling) {
// given
var taskShape = elementRegistry.get('Task_1'),
task = taskShape.businessObject;
// when
modeling.removeShape(taskShape);
// then
expect(task.$parent).to.be.null;
}));
});
describe('undo support', function() {
it('should undo', inject(function(elementRegistry, modeling, commandStack) {
// given
var taskShape = elementRegistry.get('Task_1'),
task = taskShape.businessObject,
parent = task.$parent;
// when
modeling.removeShape(taskShape);
commandStack.undo();
// then
expect(task.$parent).to.eql(parent);
}));
});
describe('redo support', function() {
it('redo', inject(function(elementRegistry, modeling, commandStack) {
// given
var taskShape = elementRegistry.get('Task_1'),
task = taskShape.businessObject;
// when
modeling.removeShape(taskShape);
commandStack.undo();
commandStack.redo();
// then
expect(task.$parent).to.be.null;
}));
});
});
describe('features/modeling - #removeShape - cropping', function() {
var diagramXML = require('./DeleteShape.cropping.bpmn');
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
it('should crop waypoints on undo/redo', inject(
function(elementRegistry, commandStack, modeling) {
// given
var taskShape = elementRegistry.get('Task_A'),
sequenceFlowConnection = elementRegistry.get('SequenceFlow_1'),
incomingFlow = taskShape.incoming[0],
outgoingFlow = taskShape.outgoing[0],
expectedStart = incomingFlow.waypoints[0],
expectedEnd = outgoingFlow.waypoints[1];
// when
modeling.removeShape(taskShape);
commandStack.undo();
commandStack.redo();
// then
expect(sequenceFlowConnection).to.have.waypoints([ expectedStart, expectedEnd ]);
}
));
});
================================================
FILE: test/spec/features/modeling/DropSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import modelingModule from 'lib/features/modeling';
import coreModule from 'lib/core';
describe('features/move - drop', function() {
var diagramXML = require('../../../fixtures/bpmn/features/drop/drop.bpmn');
var diagramXML2 = require('../../../fixtures/bpmn/features/drop/recursive-task.bpmn');
var testModules = [ coreModule, modelingModule ];
describe('elements', function() {
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
it('should update parent', inject(function(elementRegistry, modeling) {
// given
var task_1 = elementRegistry.get('ID_Task_1'),
parent = elementRegistry.get('ID_SubProcess_1');
// when
modeling.moveShape(task_1, { x: 0, y: 200 }, parent);
// then
expect(task_1.parent).to.eql(parent);
expect(task_1.businessObject.$parent).to.eql(parent.businessObject);
}));
it('should update parents', inject(function(elementRegistry, modeling) {
// given
var task_1 = elementRegistry.get('ID_Task_1'),
task_2 = elementRegistry.get('ID_Task_2'),
parent = elementRegistry.get('ID_SubProcess_1');
// when
modeling.moveElements([ task_1, task_2 ], { x: 0, y: 200 }, parent);
// then
expect(task_1.parent).to.eql(parent);
expect(task_1.businessObject.$parent).to.eql(parent.businessObject);
expect(task_2.parent).to.eql(parent);
expect(task_2.businessObject.$parent).to.eql(parent.businessObject);
}));
});
describe('connection handling', function() {
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
it('should remove flow if target and source have different parents', inject(
function(elementRegistry, modeling) {
// given
var task_1 = elementRegistry.get('ID_Task_1'),
parent = elementRegistry.get('ID_SubProcess_1'),
flow = elementRegistry.get('ID_Sequenceflow_1');
// when
modeling.moveElements([ task_1 ], { x: 0, y: 200 }, parent);
// then
expect(flow.parent).to.be.null;
expect(flow.businessObject.$parent).to.be.null;
}
));
it('should update flow parent if target and source have same parents', inject(
function(elementRegistry, modeling) {
// given
var task_1 = elementRegistry.get('ID_Task_1'),
task_2 = elementRegistry.get('ID_Task_2'),
parent = elementRegistry.get('ID_SubProcess_1'),
flow = elementRegistry.get('ID_Sequenceflow_1');
// when
modeling.moveElements([ task_1, task_2 ], { x: 0, y: 250 }, parent);
// then
expect(flow.parent).to.eql(parent);
expect(flow.businessObject.$parent).to.eql(parent.businessObject);
}
));
});
describe('recursion', function() {
beforeEach(bootstrapModeler(diagramXML2, { modules: testModules }));
it('should update parent', inject(function(elementRegistry, modeling) {
// given
var task_1 = elementRegistry.get('ID_task_1'),
parent = elementRegistry.get('ID_subprocess_1'),
sequenceFlow = elementRegistry.get('ID_sequenceflow_1');
// when
modeling.moveElements([ task_1 ], { x: 0, y: 200 }, parent);
// then
expect(task_1.parent).to.eql(parent);
expect(task_1.businessObject.$parent).to.eql(parent.businessObject);
expect(sequenceFlow.parent).to.eql(parent);
expect(sequenceFlow.businessObject.$parent).to.eql(parent.businessObject);
}));
});
});
================================================
FILE: test/spec/features/modeling/ElementFactory.bpmn
================================================
================================================
FILE: test/spec/features/modeling/ElementFactorySpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import coreModule from 'lib/core';
import modelingModule from 'lib/features/modeling';
import {
getBusinessObject,
is
} from '../../../../lib/util/ModelUtil';
import {
assign
} from 'min-dash';
describe('features - element factory', function() {
var diagramXML = require('./ElementFactory.bpmn');
var testModules = [ modelingModule, coreModule ];
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
describe('basics', function() {
it('should not mutate attrs', inject(function(elementFactory) {
// given
var attrs = {
type: 'bpmn:SubProcess',
isExpanded: false
};
// when
var createAttrs = assign({}, attrs);
elementFactory.createShape(createAttrs);
// then
expect(createAttrs).to.eql(attrs);
}));
it('should not mutate attr', inject(function(elementFactory) {
// given
var attrs = {
type: 'bpmn:SubProcess',
isExpanded: false,
di: {
'bioc:stroke': 'red'
}
};
// when
var createAttrs = assign({}, attrs);
elementFactory.createShape(createAttrs);
// then
expect(createAttrs).to.eql(attrs);
}));
});
describe('create', function() {
it('should create with message event definition', inject(function(elementFactory) {
// when
var intermediateThrowEvent = elementFactory.createShape({
type: 'bpmn:IntermediateThrowEvent',
eventDefinitionType: 'bpmn:MessageEventDefinition'
});
// then
expect(intermediateThrowEvent).to.exist;
expect(is(intermediateThrowEvent, 'bpmn:IntermediateThrowEvent')).to.be.true;
var intermediateThrowEventBo = getBusinessObject(intermediateThrowEvent),
eventDefinitions = intermediateThrowEventBo.eventDefinitions;
expect(eventDefinitions).to.exist;
expect(eventDefinitions).to.have.length(1);
var messageEventDefinition = eventDefinitions[ 0 ];
expect(is(messageEventDefinition, 'bpmn:MessageEventDefinition')).to.be.true;
}));
it('should create event with conditional event definition', inject(function(elementFactory) {
// when
var intermediateCatchEvent = elementFactory.createShape({
type: 'bpmn:IntermediateCatchEvent',
eventDefinitionType: 'bpmn:ConditionalEventDefinition'
});
// then
expect(intermediateCatchEvent).to.exist;
expect(is(intermediateCatchEvent, 'bpmn:IntermediateCatchEvent')).to.be.true;
var intermediateThrowEventBo = getBusinessObject(intermediateCatchEvent),
eventDefinitions = intermediateThrowEventBo.eventDefinitions;
expect(eventDefinitions).to.exist;
expect(eventDefinitions).to.have.length(1);
var conditionalEventDefinition = eventDefinitions[ 0 ];
expect(is(conditionalEventDefinition, 'bpmn:ConditionalEventDefinition')).to.be.true;
expect(conditionalEventDefinition.condition).to.exist;
expect(is(conditionalEventDefinition.condition, 'bpmn:FormalExpression')).to.be.true;
}));
it('should create with link event definition', inject(function(elementFactory) {
// when
var intermediateThrowEvent = elementFactory.createShape({
type: 'bpmn:IntermediateThrowEvent',
eventDefinitionType: 'bpmn:LinkEventDefinition',
eventDefinitionAttrs: {
name: ''
}
});
// then
expect(intermediateThrowEvent).to.exist;
expect(is(intermediateThrowEvent, 'bpmn:IntermediateThrowEvent')).to.be.true;
var intermediateThrowEventBo = getBusinessObject(intermediateThrowEvent),
eventDefinitions = intermediateThrowEventBo.eventDefinitions;
expect(eventDefinitions).to.exist;
expect(eventDefinitions).to.have.length(1);
var eventDefinition = eventDefinitions[ 0 ];
expect(is(eventDefinition, 'bpmn:LinkEventDefinition')).to.be.true;
expect(eventDefinition.name).to.eql('');
}));
it('should error when accessing via businessObject', inject(function(elementFactory) {
// given
var shape = elementFactory.createShape({
type: 'bpmn:Task',
});
// then
expect(shape.di).to.exist;
expect(function() {
shape.businessObject.di;
}).to.throw(/The di is available through the diagram element only./);
}));
it('should add collapsed attribute to subprocess', inject(function(elementFactory) {
// when
var subprocess = elementFactory.createShape({
type: 'bpmn:SubProcess',
isExpanded: false
});
// then
expect(subprocess.collapsed).to.be.true;
}));
it('should create subprocess as event subprocess', inject(function(elementFactory) {
// when
var subprocess = elementFactory.createShape({
type: 'bpmn:SubProcess',
triggeredByEvent: true
});
var businessObject = getBusinessObject(subprocess);
// then
expect(businessObject.triggeredByEvent).to.be.true;
}));
it('should create boundary event as non-interrupting', inject(function(elementFactory) {
// when
var event = elementFactory.createShape({
type: 'bpmn:BoundaryEvent',
eventDefinitionType: 'bpmn:MessageEventDefinition',
cancelActivity: false
});
var businessObject = getBusinessObject(event);
// then
expect(businessObject.cancelActivity).to.be.false;
}));
it('should create exclusive gateway with x marker', inject(function(elementFactory) {
// when
var shape = elementFactory.createShape({
type: 'bpmn:ExclusiveGateway',
di: { isMarkerVisible: true }
});
// then
expect(shape.di.isMarkerVisible).to.be.true;
}));
it('should create exclusive gateway without x marker', inject(function(elementFactory) {
// when
var shape = elementFactory.createShape({
type: 'bpmn:ExclusiveGateway',
di: { isMarkerVisible: false }
});
// then
expect(shape.di.isMarkerVisible).to.be.false;
}));
it('should create exclusive gateway with x marker by default', inject(function(elementFactory) {
// when
var shape = elementFactory.createShape({
type: 'bpmn:ExclusiveGateway'
});
// then
expect(shape.di.isMarkerVisible).to.be.true;
}));
it('should create horizontal participant', inject(function(elementFactory) {
// when
var shape = elementFactory.createParticipantShape({
isHorizontal: true
});
// then
expect(shape.di.isHorizontal).to.be.true;
}));
it('should create vertical participant', inject(function(elementFactory) {
// when
var shape = elementFactory.createParticipantShape({
isHorizontal: false
});
// then
expect(shape.di.isHorizontal).to.be.false;
}));
it('should create horizontal lane', inject(function(elementFactory) {
// when
var shape = elementFactory.createShape({
type: 'bpmn:Lane',
isHorizontal: true
});
// then
expect(shape.di.isHorizontal).to.be.true;
}));
it('should create vertical lane', inject(function(elementFactory) {
// when
var shape = elementFactory.createShape({
type: 'bpmn:Lane',
isHorizontal: false
});
// then
expect(shape.di.isHorizontal).to.be.false;
}));
describe('integration', function() {
it('should create event definition with ID', inject(function(elementFactory) {
// when
var intermediateThrowEvent = elementFactory.createShape({
type: 'bpmn:IntermediateThrowEvent',
eventDefinitionType: 'bpmn:MessageEventDefinition'
});
// then
var intermediateThrowEventBo = getBusinessObject(intermediateThrowEvent),
eventDefinitions = intermediateThrowEventBo.eventDefinitions,
messageEventDefinition = eventDefinitions[ 0 ];
expect(messageEventDefinition.id).to.exist;
}));
it('should NOT create formal expression with ID', inject(function(elementFactory) {
// when
var intermediateCatchEvent = elementFactory.createShape({
type: 'bpmn:IntermediateCatchEvent',
eventDefinitionType: 'bpmn:ConditionalEventDefinition'
});
// then
var intermediateThrowEventBo = getBusinessObject(intermediateCatchEvent),
eventDefinitions = intermediateThrowEventBo.eventDefinitions,
conditionalEventDefinition = eventDefinitions[ 0 ];
expect(conditionalEventDefinition.condition.id).not.to.exist;
}));
});
});
});
================================================
FILE: test/spec/features/modeling/IdClaim.bpmn
================================================
================================================
FILE: test/spec/features/modeling/IdClaimSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import modelingModule from 'lib/features/modeling';
import coreModule from 'lib/core';
describe('features/modeling - id claim management', function() {
var testModules = [ coreModule, modelingModule ];
var processDiagramXML = require('./IdClaim.bpmn');
beforeEach(bootstrapModeler(processDiagramXML, { modules: testModules }));
var element, moddleElement, id;
beforeEach(inject(function(elementRegistry, moddle) {
id = 'StartEvent_2';
element = elementRegistry.get(id);
moddleElement = element.businessObject;
}));
describe('unclaim', function() {
it('should unclaim id when removing element', inject(function(modeling, moddle) {
// when
modeling.removeElements([ element ]);
// then
expect(moddle.ids.assigned(id)).to.be.false;
}));
it('should revert unclaim action on restoring element', inject(function(modeling, moddle, commandStack) {
// given
modeling.removeElements([ element ]);
// when
commandStack.undo();
// then
expect(moddle.ids.assigned(id)).to.eql(moddleElement);
}));
});
});
================================================
FILE: test/spec/features/modeling/LabelBoundsSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import Modeler from 'lib/Modeler';
import TestContainer from 'mocha-test-container-support';
var DELTA = 2;
describe('label bounds', function() {
function createModeler(xml) {
var modeler = new Modeler({ container: container });
return modeler.importXML(xml).then(function(result) {
return { error: null, warnings: result.warnings, modeler: modeler };
}).catch(function(err) {
return { error: err, warnings: err.warnings, modeler: modeler };
});
}
var container;
beforeEach(function() {
container = TestContainer.get(this);
});
describe('on import', function() {
it('should import simple label process', function() {
var xml = require('./LabelBoundsSpec.simple.bpmn');
return createModeler(xml).then(function(result) {
expect(result.error).not.to.exist;
});
});
});
describe('on label change', function() {
var diagramXML = require('./LabelBoundsSpec.simple.bpmn');
beforeEach(bootstrapModeler(diagramXML));
var updateLabel;
beforeEach(inject(function(directEditing) {
updateLabel = function(shape, text) {
directEditing.activate(shape);
directEditing._textbox.content.innerText = text;
directEditing.complete();
};
}));
describe('label dimensions', function() {
it('should expand width', inject(function(elementRegistry) {
// given
var shape = elementRegistry.get('StartEvent_1'),
oldLabelWidth = shape.label.width;
// when
updateLabel(shape, 'Foooooooooobar');
// then
expect(shape.label.width).to.be.above(oldLabelWidth);
}));
it('should expand height', inject(function(elementRegistry) {
// given
var shape = elementRegistry.get('StartEvent_1'),
oldLabelHeight = shape.label.height;
// when
updateLabel(shape, 'Foo\nbar\nbaz');
// then
expect(shape.label.height).to.be.above(oldLabelHeight);
}));
it('should reduce width', inject(function(elementRegistry) {
// given
var shape = elementRegistry.get('StartEvent_1'),
oldLabelWidth = shape.label.width;
// when
updateLabel(shape, 'i');
// then
expect(shape.label.width).to.be.below(oldLabelWidth);
}));
it('should reduce height', inject(function(elementRegistry) {
// given
var shape = elementRegistry.get('StartEvent_3'),
oldLabelHeight = shape.label.height;
// when
updateLabel(shape, 'One line');
// then
expect(shape.label.height).to.be.below(oldLabelHeight);
}));
});
describe('label position', function() {
var getExpectedX = function(shape) {
return Math.round(shape.x + shape.width / 2 - shape.label.width / 2);
};
it('should shift to left', inject(function(elementRegistry) {
// given
var shape = elementRegistry.get('StartEvent_1');
// when
updateLabel(shape, 'Foooooooooobar');
// then
var expectedX = getExpectedX(shape);
expect(shape.label.x).to.be.closeTo(expectedX, DELTA);
}));
it('should shift to right', inject(function(elementRegistry) {
// given
var shape = elementRegistry.get('StartEvent_1');
// when
updateLabel(shape, 'F');
// then
var expectedX = getExpectedX(shape);
expect(shape.label.x).to.equal(expectedX);
}));
it('should remain the same', inject(function(elementRegistry) {
// given
var shape = elementRegistry.get('StartEvent_1');
// when
updateLabel(shape, 'FOOBAR');
// copy old horizontal label position
var oldX = shape.label.x + 0;
updateLabel(shape, 'FOOBAR\n1');
// then
expect(shape.label.x).to.equal(oldX);
}));
});
describe('label outlines', function() {
it('should update after element bounds have been updated',
inject(function(outline, elementRegistry, bpmnRenderer) {
// given
var shape = elementRegistry.get('StartEvent_1');
var outlineSpy = sinon.spy(outline, 'updateShapeOutline');
var rendererSpy = sinon.spy(bpmnRenderer, 'drawShape');
// when
updateLabel(shape, 'Fooooobar');
// then
// expect the outline updating to happen after the renderer
// updated the elements bounds dimensions and position
sinon.assert.callOrder(
rendererSpy.withArgs(sinon.match.any, shape.label),
outlineSpy.withArgs(sinon.match.any, shape.label)
);
})
);
});
describe('interaction events', function() {
it('should update bounds after element bounds have been updated',
inject(function(interactionEvents, elementRegistry, bpmnRenderer, graphicsFactory) {
// given
var shape = elementRegistry.get('StartEvent_1');
var graphicsFactorySpy = sinon.spy(graphicsFactory, 'update'),
rendererSpy = sinon.spy(bpmnRenderer, 'drawShape');
// when
updateLabel(shape, 'Fooooobar');
// then
// expect the interaction event bounds updating to happen after the renderer
// updated the elements bounds dimensions and position
sinon.assert.callOrder(
rendererSpy.withArgs(sinon.match.any, shape.label),
graphicsFactorySpy
);
})
);
});
});
describe('on export', function() {
it('should create DI when label has changed', function() {
var xml = require('./LabelBoundsSpec.simple.bpmn');
var shape;
return createModeler(xml).then(function(result) {
var err = result.error;
var modeler = result.modeler;
if (err) {
throw err;
}
var elementRegistry = modeler.get('elementRegistry'),
directEditing = modeler.get('directEditing');
shape = elementRegistry.get('StartEvent_1');
directEditing.activate(shape);
directEditing._textbox.content.innerText = 'BARBAZ';
directEditing.complete();
return modeler.saveXML({ format: true });
}).then(function(result) {
var xml = result.xml;
// strip spaces and line breaks after '>'
xml = xml.replace(/>\s+/g,'>');
// get label width and height from XML
var matches = xml.match(/StartEvent_1_di.*?BPMNLabel.*?width="(\d*).*?height="(\d*)/);
var width = parseInt(matches[1]),
height = parseInt(matches[2]);
expect(width).to.equal(shape.label.width);
expect(height).to.equal(shape.label.height);
});
});
it('should update existing DI when label has changed', function() {
var xml = require('./LabelBoundsSpec.simple.bpmn');
var shape;
return createModeler(xml).then(function(result) {
var err = result.error;
var modeler = result.modeler;
if (err) {
throw err;
}
var elementRegistry = modeler.get('elementRegistry'),
directEditing = modeler.get('directEditing');
shape = elementRegistry.get('StartEvent_3');
directEditing.activate(shape);
directEditing._textbox.content.innerText = 'BARBAZ';
directEditing.complete();
return modeler.saveXML({ format: true });
}).then(function(result) {
var xml = result.xml;
// strip spaces and line breaks after '>'
xml = xml.replace(/>\s+/g,'>');
// get label width and height from XML
var matches = xml.match(/StartEvent_3_di.*?BPMNLabel.*?width="(\d*).*?height="(\d*)/);
var width = parseInt(matches[1]),
height = parseInt(matches[2]);
expect(width).to.equal(shape.label.width);
expect(height).to.equal(shape.label.height);
});
});
it('should not update DI of unchanged labels', function() {
var xml = require('./LabelBoundsSpec.simple.bpmn');
// strip windows line breaks (if any)
xml = xml.replace(/\r/g, '');
return createModeler(xml).then(function(result) {
var err = result.error;
var modeler = result.modeler;
if (err) {
throw err;
}
return modeler.saveXML({ format: true });
}).then(function(result) {
var savedXML = result.xml;
expect(savedXML).to.equal(xml);
});
});
});
});
================================================
FILE: test/spec/features/modeling/LabelBoundsSpec.simple.bpmn
================================================
================================================
FILE: test/spec/features/modeling/LabelLayouting.initial.bpmn
================================================
================================================
FILE: test/spec/features/modeling/LabelLayouting.integration.bpmn
================================================
Flow_1
Flow_1
================================================
FILE: test/spec/features/modeling/LabelLayouting.move.bpmn
================================================
SequenceFlow_A
SequenceFlow_A
SequenceFlow_B
SequenceFlow_B
SequenceFlow_C
SequenceFlow_D
SequenceFlow_C
SequenceFlow_D
SequenceFlow_E
SequenceFlow_E
================================================
FILE: test/spec/features/modeling/LabelLayoutingSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import coreModule from 'lib/core';
import bendpointsModule from 'diagram-js/lib/features/bendpoints';
import modelingModule from 'lib/features/modeling';
import labelEditingModule from 'lib/features/label-editing';
import spaceTool from 'diagram-js/lib/features/space-tool';
import {
createCanvasEvent as canvasEvent
} from '../../../util/MockEvents';
var testModules = [
coreModule,
modelingModule,
labelEditingModule,
bendpointsModule,
spaceTool
];
describe('modeling - label layouting', function() {
describe('should position created label', function() {
var diagramXML = require('./LabelLayouting.initial.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
it('horizontal', inject(function(modeling, elementRegistry) {
// given
var element1 = elementRegistry.get('StartEvent_1'),
element2 = elementRegistry.get('ExclusiveGateway_2');
// when
var connection = modeling.connect(element1, element2);
modeling.updateLabel(connection, 'foo');
// then
expect(connection.label.x).to.be.within(463, 465);
expect(connection.label.y).to.be.within(335, 340);
}));
it('vertical', inject(function(modeling, elementRegistry) {
// given
var element1 = elementRegistry.get('StartEvent_1'),
element2 = elementRegistry.get('ExclusiveGateway_1');
// when
var connection = modeling.connect(element1, element2);
modeling.updateLabel(connection, 'foo');
// then
expect(connection.label.x).to.be.within(328, 330);
expect(connection.label.y).to.be.within(225, 230);
}));
});
describe('should move label', function() {
var diagramXML = require('./LabelLayouting.move.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
describe('on segment move', function() {
it('left - no relayout', inject(function(elementRegistry, connectionSegmentMove, dragging) {
// given
var connection = elementRegistry.get('SequenceFlow_B'),
labelPosition = getLabelPosition(connection);
// when
connectionSegmentMove.start(canvasEvent({ x: 0, y: 0 }), connection, 2);
dragging.move(canvasEvent({ x: -30, y: 0 }));
dragging.end();
// then
expectLabelMoved(connection, labelPosition, { x: -30, y: 0 });
}));
it('left - remove bendpoint', inject(
function(elementRegistry, connectionSegmentMove, dragging) {
// given
var connection = elementRegistry.get('SequenceFlow_B'),
labelPosition = getLabelPosition(connection);
// when
connectionSegmentMove.start(canvasEvent({ x: 0, y: 0 }), connection, 2);
dragging.move(canvasEvent({ x: -70, y: 0 }));
dragging.end();
// then
expectLabelMoved(connection, labelPosition, { x: -70, y: 24 });
}
));
it('right - no relayout', inject(
function(elementRegistry, connectionSegmentMove, dragging) {
// given
var connection = elementRegistry.get('SequenceFlow_B'),
labelPosition = getLabelPosition(connection);
// when
connectionSegmentMove.start(canvasEvent({ x: 0, y: 0 }), connection, 2);
dragging.move(canvasEvent({ x: 30, y: 0 }));
dragging.end();
// then
expectLabelMoved(connection, labelPosition, { x: 30, y: 0 });
}
));
it('right - remove bendpoint', inject(
function(elementRegistry, connectionSegmentMove, dragging) {
// given
var connection = elementRegistry.get('SequenceFlow_B'),
labelPosition = getLabelPosition(connection);
// when
connectionSegmentMove.start(canvasEvent({ x: 0, y: 0 }), connection, 2);
dragging.move(canvasEvent({ x: 70, y: 0 }));
dragging.end();
// then
expectLabelMoved(connection, labelPosition, { x: 70, y: -16 });
}
));
it('down', inject(function(elementRegistry, connectionSegmentMove, dragging) {
// given
var connection = elementRegistry.get('SequenceFlow_C'),
labelPosition = getLabelPosition(connection);
// when
connectionSegmentMove.start(canvasEvent({ x: 0, y: 0 }), connection, 2);
dragging.move(canvasEvent({ x: 0, y: 70 }));
dragging.end();
// then
expectLabelMoved(connection, labelPosition, { x: 0, y: 70 });
}));
it('up - remove two bendpoints', inject(
function(elementRegistry, connectionSegmentMove, dragging) {
// given
var connection = elementRegistry.get('SequenceFlow_C'),
labelPosition = getLabelPosition(connection);
// when
connectionSegmentMove.start(canvasEvent({ x: 0, y: 0 }), connection, 2);
dragging.move(canvasEvent({ x: 0, y: -90 }));
dragging.end();
// then
expectLabelMoved(connection, labelPosition, { x: -39, y: -85 });
}
));
it('up - remove two bendpoints - redundant waypoints', inject(
function(elementRegistry, connectionSegmentMove, dragging, bendpointMove) {
// given
var connection = elementRegistry.get('SequenceFlow_C');
bendpointMove.start(canvasEvent({ x: 0, y: 0 }), connection, 1);
dragging.move(canvasEvent({ x: 620, y: 435 }));
dragging.end();
bendpointMove.start(canvasEvent({ x: 0, y: 0 }), connection, 2);
dragging.move(canvasEvent({ x: 300, y: 435 }));
dragging.end();
var labelPosition = getLabelPosition(connection);
// when
connectionSegmentMove.start(canvasEvent({ x: 0, y: 0 }), connection, 2);
dragging.move(canvasEvent({ x: 0, y: -160 }));
dragging.end();
// then
expect(getLabelPosition(connection)).not.to.eql(labelPosition);
}
));
});
describe('on reconnect', function() {
it('start', inject(function(elementRegistry, modeling) {
// given
var connection = elementRegistry.get('SequenceFlow_D'),
shape = elementRegistry.get('Task_1');
// when
modeling.reconnectStart(connection, shape, { x: 0, y: 0 });
// then
expect(Math.round(connection.label.x)).to.be.within(570, 575);
expect(Math.round(connection.label.y)).to.be.within(136, 138);
}));
it('end', inject(function(elementRegistry, modeling) {
// given
var connection = elementRegistry.get('SequenceFlow_A'),
shape = elementRegistry.get('Task_2');
// when
modeling.reconnectEnd(connection, shape, { x: 294, y: 270 });
// then
expect(Math.round(connection.label.x)).to.be.within(257, 270);
expect(Math.round(connection.label.y)).to.be.within(240, 250);
}));
});
describe('on shape move', function() {
it('down', inject(function(elementRegistry, modeling) {
// given
var connection = elementRegistry.get('SequenceFlow_E'),
shape = elementRegistry.get('Task_4'),
labelPosition = getLabelPosition(connection);
// when
modeling.moveShape(shape, { x: 0, y: 100 });
// then
expectLabelMoved(connection, labelPosition, { x: 0, y: 100 });
}));
});
describe('on bendpoint add/delete/moving', function() {
it('move, label on segment', inject(function(elementRegistry, bendpointMove, dragging) {
// given
var connection = elementRegistry.get('SequenceFlow_B');
// when
bendpointMove.start(canvasEvent({ x: 0, y: 0 }), connection, 1);
dragging.move(canvasEvent({ x: 455 + 50, y: 120 }));
dragging.end();
// then
expect(Math.round(connection.label.x)).to.be.within(466, 468);
expect(Math.round(connection.label.y)).to.be.within(170, 171);
}));
it('move, label on bendpoint', inject(
function(elementRegistry, bendpointMove, dragging, modeling) {
// given
var connection = elementRegistry.get('SequenceFlow_C');
// label out of segments, on a bendpoint (idx=1)
modeling.moveShape(connection.label, { x: 40, y: 0 });
// when
bendpointMove.start(canvasEvent({ x: 0, y: 0 }), connection, 1);
dragging.move(canvasEvent({ x: 455 + 50, y: 500 }));
dragging.end();
// then
expect(Math.round(connection.label.x)).to.be.within(517, 519);
expect(Math.round(connection.label.y)).to.be.equal(507);
}
));
it('remove bendpoint when label on segment', inject(
function(elementRegistry, bendpointMove, dragging) {
// given
var connection = elementRegistry.get('SequenceFlow_B');
// when
bendpointMove.start(canvasEvent({ x: 0, y: 0 }), connection, 1);
dragging.move(canvasEvent({ x: 455, y: 120 + 160 }));
dragging.end();
// then
expect(Math.round(connection.label.x)).to.be.within(418, 421);
expect(Math.round(connection.label.y)).to.be.equal(191);
}
));
it('add bendpoint, label on right segment', inject(
function(elementRegistry, bendpointMove, dragging, canvas) {
// given
var connection = elementRegistry.get('SequenceFlow_A');
// when
bendpointMove.start(canvasEvent({ x: 0, y: 0 }), connection, 1, true);
dragging.hover({
element: connection,
gfx: canvas.getGraphics(connection)
});
dragging.move(canvasEvent({ x: 220, y: 200 }));
dragging.end();
// then
expect(Math.round(connection.label.x)).to.be.within(248, 251);
expect(Math.round(connection.label.y)).to.be.within(151, 152);
}
));
it('add bendpoint, label on left segment', inject(
function(elementRegistry, bendpointMove, dragging, canvas) {
// given
var connection = elementRegistry.get('SequenceFlow_A');
// when
bendpointMove.start(canvasEvent({ x: 0, y: 0 }), connection, 1, true);
dragging.hover({
element: connection,
gfx: canvas.getGraphics(connection)
});
dragging.move(canvasEvent({ x: 260, y: 200 }));
dragging.end();
// then
expect(connection.label.x).to.be.within(240, 242);
expect(connection.label.y).to.be.within(148, 149);
}
));
it('remove bendpoint when label on bendpoint', inject(
function(elementRegistry, bendpointMove, dragging, modeling) {
// given
var connection = elementRegistry.get('SequenceFlow_C');
// label out of segments, on a bendpoint
modeling.moveShape(connection.label, { x: 40, y: 0 });
// when
bendpointMove.start(canvasEvent({ x: 0, y: 0 }), connection, 1);
dragging.move(canvasEvent({ x: 455, y: 320 }));
dragging.end();
// then
expect(Math.round(connection.label.x)).to.be.within(462, 465);
expect(Math.round(connection.label.y)).to.be.within(290, 293);
}
));
it('add benpoint, label on segment, should not move', inject(
function(elementRegistry, bendpointMove, canvas, dragging, modeling) {
// given
var connection = elementRegistry.get('SequenceFlow_C');
// label out of segments, on a bendpoint
modeling.moveShape(connection.label, { x: 40, y: -60 });
var position = getLabelPosition(connection);
// when
bendpointMove.start(canvasEvent({ x: 0, y: 0 }), connection, 2, true);
dragging.hover({
element: connection,
gfx: canvas.getGraphics(connection)
});
dragging.move(canvasEvent({ x: 400, y: 350 }));
dragging.end();
// then
expectLabelMoved(connection, position, { x: 0, y: 0 });
}
));
});
describe('special cases', function() {
it('should behave properly, right after importing', inject(
function(elementRegistry, connectionSegmentMove, dragging, modeling) {
// given
var connection = elementRegistry.get('SequenceFlow_C'),
labelPosition = getLabelPosition(connection),
label = connection.label;
// when
connectionSegmentMove.start(canvasEvent({ x: 0, y: 0 }), connection, 2);
dragging.move(canvasEvent({ x: 0, y: 70 }));
dragging.end();
// move label
modeling.moveShape(label, { x: 40, y: -30 });
// drag again
connectionSegmentMove.start(canvasEvent({ x: 0, y: 0 }), connection, 1);
dragging.move(canvasEvent({ x: -20, y: 0 }));
dragging.end();
// then
expectLabelMoved(connection, labelPosition, { x: 20, y: 40 });
}
));
it('should reposition on right segment', inject(
function(elementRegistry, connectionSegmentMove, dragging) {
// given
var connection = elementRegistry.get('SequenceFlow_E'),
labelPosition = getLabelPosition(connection);
// when
connectionSegmentMove.start(canvasEvent({ x: 0, y: 0 }), connection, 2);
dragging.move(canvasEvent({ x: -100, y: 0 }));
dragging.end();
// then
expect(connection.label.y - labelPosition.y).to.be.within(-77, -73);
expect(connection.label.x - labelPosition.x).to.be.within(-54, -51);
}
));
});
});
describe('integration', function() {
describe('space tool', function() {
var diagramXML = require('./LabelLayouting.integration.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
beforeEach(inject(function(dragging) {
dragging.setOptions({ manual: true });
}));
it('should not throw on waypoints including float', inject(function(spaceTool, dragging) {
// given
// when
// then
expect(function() {
spaceTool.activateMakeSpace(canvasEvent({ x: 200, y: 0 }));
dragging.move(canvasEvent({ x: 300, y: 0 }));
dragging.end();
}).not.to.throw();
}));
});
});
});
function getLabelPosition(connection) {
var label = connection.label;
var mid = {
x: label.x + (label.width / 2),
y: label.y + (label.height / 2)
};
return mid;
}
function expectLabelMoved(connection, oldPosition, expectedDelta) {
var newPosition = getLabelPosition(connection);
var delta = {
x: Math.round(newPosition.x - oldPosition.x),
y: Math.round(newPosition.y - oldPosition.y)
};
expect(delta).to.eql(expectedDelta);
}
================================================
FILE: test/spec/features/modeling/LoggingCroppingConnectionDocking.js
================================================
import CroppingConnectionDocking from 'diagram-js/lib/layout/CroppingConnectionDocking';
import {
getOrientation
} from 'diagram-js/lib/layout/LayoutUtil';
import inherits from 'inherits-browser';
export default function LoggingCroppingConnectionDocking(injector) {
injector.invoke(CroppingConnectionDocking, this);
}
LoggingCroppingConnectionDocking.$inject = [
'injector'
];
inherits(LoggingCroppingConnectionDocking, CroppingConnectionDocking);
window.noIntersectCount = 0;
window.noIntersect = [];
LoggingCroppingConnectionDocking.prototype._getIntersection = function(shape, connection, takeFirst) {
var intersection = CroppingConnectionDocking.prototype._getIntersection.call(this, shape, connection, takeFirst);
if (!intersection) {
if (getOrientation(connection.source, connection.target) !== 'intersect') {
window.noIntersectCount++;
window.noIntersect.push([
connection,
this._getShapePath(shape),
this._getConnectionPath(connection)
]);
}
}
return intersection;
};
================================================
FILE: test/spec/features/modeling/MoveConnectionSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import modelingModule from 'lib/features/modeling';
import coreModule from 'lib/core';
import { getDi } from 'lib/util/ModelUtil';
describe('features/modeling - move connection', function() {
describe('should move connection', function() {
var diagramXML = require('../../../fixtures/bpmn/sequence-flows.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: [
coreModule,
modelingModule
]
}));
it('execute', inject(function(elementRegistry, modeling, bpmnFactory) {
// given
var sequenceFlowConnection = elementRegistry.get('SequenceFlow_1'),
sequenceFlowDi = getDi(sequenceFlowConnection);
// when
modeling.moveConnection(sequenceFlowConnection, { x: 20, y: 10 });
var expectedWaypoints = [
{ x: 598, y: 351 },
{ x: 954, y: 351 },
{ x: 954, y: 446 },
{ x: 852, y: 446 }
];
// then
// expect cropped connection
expect(sequenceFlowConnection).to.have.waypoints(expectedWaypoints);
// expect cropped waypoints in di
var diWaypoints = bpmnFactory.createDiWaypoints(expectedWaypoints);
expect(sequenceFlowDi.waypoint).eql(diWaypoints);
}));
it('undo', inject(function(elementRegistry, commandStack, modeling) {
// given
var sequenceFlowConnection = elementRegistry.get('SequenceFlow_1'),
sequenceFlowDi = getDi(sequenceFlowConnection);
var oldWaypoints = sequenceFlowConnection.waypoints,
oldDiWaypoints = sequenceFlowDi.waypoint;
modeling.moveConnection(sequenceFlowConnection, { x: 20, y: 10 });
// when
commandStack.undo();
// then
expect(sequenceFlowConnection.waypoints).eql(oldWaypoints);
expect(sequenceFlowDi.waypoint).eql(oldDiWaypoints);
}));
it('redo', inject(function(elementRegistry, commandStack, modeling) {
// given
var sequenceFlowConnection = elementRegistry.get('SequenceFlow_1'),
sequenceFlowDi = getDi(sequenceFlowConnection);
modeling.moveConnection(sequenceFlowConnection, { x: 20, y: 10 });
var newWaypoints = sequenceFlowConnection.waypoints,
newDiWaypoints = sequenceFlowDi.waypoint;
// when
commandStack.undo();
commandStack.redo();
// then
expect(sequenceFlowConnection.waypoints).eql(newWaypoints);
expect(sequenceFlowDi.waypoint).eql(newDiWaypoints);
}));
});
});
================================================
FILE: test/spec/features/modeling/MoveElements.boundary-connection.bpmn
================================================
Task_Flow
Task_Flow
Boundary_Flow
Boundary_Flow
================================================
FILE: test/spec/features/modeling/MoveElements.centered-connection.bpmn
================================================
SequenceFlow_1
SequenceFlow_1
================================================
FILE: test/spec/features/modeling/MoveElements.collaboration-association.bpmn
================================================
prepareBankTransfer
Prepare the bank transfer.
================================================
FILE: test/spec/features/modeling/MoveElements.data-input-output.bpmn
================================================
DataInput
DataOutput
_9628422b-85a6-4857-8c14-7289b9fd9a8a
_29b8c649-e2a0-4dd3-804b-567e8cc71718
DataInput
_9628422b-85a6-4857-8c14-7289b9fd9a8a
_29b8c649-e2a0-4dd3-804b-567e8cc71718
DataOutput
================================================
FILE: test/spec/features/modeling/MoveElements.eventBasedTargets.bpmn
================================================
SequenceFlow_A
SequenceFlow_05bjkc2
SequenceFlow_B
SequenceFlow_1l6c300
SequenceFlow_C
SequenceFlow_0ku3hwq
SequenceFlow_D
SequenceFlow_0yjtrc2
SequenceFlow_E
SequenceFlow_12xris6
SequenceFlow_05bjkc2
SequenceFlow_1l6c300
SequenceFlow_0ku3hwq
SequenceFlow_0yjtrc2
SequenceFlow_12xris6
SequenceFlow_A
SequenceFlow_B
SequenceFlow_C
SequenceFlow_D
SequenceFlow_E
================================================
FILE: test/spec/features/modeling/MoveElements.flow-collaboration.bpmn
================================================
SequenceFlow
SequenceFlow
================================================
FILE: test/spec/features/modeling/MoveElementsSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import modelingModule from 'lib/features/modeling';
import coreModule from 'lib/core';
describe('features/modeling - move elements', function() {
describe('flow parent', function() {
var diagramXML = require('./MoveElements.flow-collaboration.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: [
coreModule,
modelingModule
]
}));
it('should keep when moving shapes', inject(function(elementRegistry, modeling, bpmnFactory) {
// given
var connectionSequenceFlow = elementRegistry.get('SequenceFlow'),
shapeTask_A = elementRegistry.get('Task_A'),
shapeTask_B = elementRegistry.get('Task_B'),
shapeTask_C = elementRegistry.get('Task_C'),
shapePool_A = elementRegistry.get('Pool_A'),
shapePool_B = elementRegistry.get('Pool_B');
// when
modeling.moveElements(
[ shapeTask_A, shapeTask_B, shapeTask_C ],
{ x: 0, y: -50 },
shapePool_B,
{ primaryShape: shapeTask_C }
);
// then
expect(connectionSequenceFlow.parent).to.eql(shapePool_A);
}));
it('should keep when moving shapes with flow', inject(function(elementRegistry, modeling, bpmnFactory) {
// given
var connectionSequenceFlow = elementRegistry.get('SequenceFlow'),
shapeTask_A = elementRegistry.get('Task_A'),
shapeTask_B = elementRegistry.get('Task_B'),
shapeTask_C = elementRegistry.get('Task_C'),
shapePool_A = elementRegistry.get('Pool_A'),
shapePool_B = elementRegistry.get('Pool_B');
// when
modeling.moveElements(
[ shapeTask_A, shapeTask_B, shapeTask_C, connectionSequenceFlow ],
{ x: 0, y: -50 },
shapePool_B,
{ primaryShape: shapeTask_C }
);
// then
expect(connectionSequenceFlow.parent).to.eql(shapePool_A);
}));
});
describe('boundary connection with tasks', function() {
var diagramXML = require('./MoveElements.boundary-connection.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: [
coreModule,
modelingModule
]
}));
it('should properly adjust connection', inject(function(elementRegistry, modeling) {
// given
var elements = [
elementRegistry.get('Task_1'),
elementRegistry.get('Task_2'),
elementRegistry.get('BoundaryEvent')
];
var boundaryFlow = elementRegistry.get('Boundary_Flow');
var delta = { x: 0, y: 20 };
var expectedWaypoints = moveWaypoints(boundaryFlow.waypoints, delta);
// when
modeling.moveElements(elements, delta);
// then
expect(boundaryFlow).to.have.waypoints(expectedWaypoints);
}));
});
describe('data input / data output', function() {
var diagramXML = require('./MoveElements.data-input-output.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: [
coreModule,
modelingModule
]
}));
it('should move', inject(function(elementRegistry, modeling) {
// given
var dataInput = elementRegistry.get('DataInput');
var dataOutput = elementRegistry.get('DataOutput');
var elements = [
dataInput,
dataOutput,
elementRegistry.get('Task')
];
// when
modeling.moveElements(
elements,
{ x: -10, y: -10 }
);
// then
expect(dataOutput).to.have.bounds({
x: 275,
y: 140,
width: 34,
height: 40
});
expect(dataInput).to.have.bounds({
x: 90,
y: 90,
width: 34,
height: 40
});
}));
it('should wrap in participant', inject(
function(elementRegistry, elementFactory, modeling, canvas) {
// given
var dataInput = elementRegistry.get('DataInput');
var dataOutput = elementRegistry.get('DataOutput');
var processShape = canvas.getRootElement(),
processBo = processShape.businessObject,
participantShape = elementFactory.createParticipantShape(true);
// when
modeling.createShape(participantShape, { x: 350, y: 200 }, processShape);
// then
expect(dataInput.businessObject.$parent).to.eql(processBo.ioSpecification);
expect(dataOutput.businessObject.$parent).to.eql(processBo.ioSpecification);
}
));
});
describe('association', function() {
var testXML = require('./MoveElements.collaboration-association.bpmn');
beforeEach(bootstrapModeler(testXML, {
modules: [
coreModule,
modelingModule
]
}));
it('move association', inject(function(elementRegistry, modeling) {
// given
var association = elementRegistry.get('Association_1'),
participant = elementRegistry.get('Process_Engine_1');
var elements = [
elementRegistry.get('DataStoreReference_1'),
association,
elementRegistry.get('prepareBankTransfer')
];
// when
modeling.moveElements(elements, { x: 10, y: 10 }, participant);
// then
expect(association.parent).to.exist;
}));
});
describe('incoming sequence flows of event based targets', function() {
var diagramXML = require('./MoveElements.eventBasedTargets.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: [
coreModule,
modelingModule
]
}));
it('should keep when moving source flow object', inject(function(elementRegistry, modeling) {
// given
var shapeTask_A = elementRegistry.get('Task_A'),
shapeTask_B = elementRegistry.get('Task_B'),
shapeTask_C = elementRegistry.get('Task_C'),
shapeTask_D = elementRegistry.get('Task_D'),
shapeTask_E = elementRegistry.get('Task_E'),
connectionSequenceFlow_A = elementRegistry.get('SequenceFlow_A'),
connectionSequenceFlow_B = elementRegistry.get('SequenceFlow_B'),
connectionSequenceFlow_C = elementRegistry.get('SequenceFlow_C'),
connectionSequenceFlow_D = elementRegistry.get('SequenceFlow_D'),
connectionSequenceFlow_E = elementRegistry.get('SequenceFlow_E');
// when
modeling.moveElements([
shapeTask_A,
shapeTask_B,
shapeTask_C,
shapeTask_D,
shapeTask_E
], {
x: 0, y: -50
});
// then
expect(elementRegistry.get(connectionSequenceFlow_A.id)).to.exist;
expect(elementRegistry.get(connectionSequenceFlow_B.id)).to.exist;
expect(elementRegistry.get(connectionSequenceFlow_C.id)).to.exist;
expect(elementRegistry.get(connectionSequenceFlow_D.id)).to.exist;
expect(elementRegistry.get(connectionSequenceFlow_E.id)).to.exist;
}));
});
describe('center-to-center connection', function() {
var diagramXML = require('./MoveElements.centered-connection.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: [
coreModule,
modelingModule
]
}));
it('should properly adjust connection', inject(function(elementRegistry, modeling) {
// given
var targetElement = elementRegistry.get('Task_2');
var sequenceFlow = elementRegistry.get('SequenceFlow_1');
// move from centric-left to centric-below
var delta = { x: -150, y: 150 };
var expectedWaypoints = [
{ x: 200, y: 160 },
{ x: 200, y: 230 }
];
// when
modeling.moveElements([ targetElement ], delta);
// then
expect(sequenceFlow).to.have.waypoints(expectedWaypoints);
}));
});
});
// helpers //////////////////////
function moveWaypoint(p, delta) {
return {
x: p.x + delta.x || 0,
y: p.y + delta.y || 0
};
}
function moveWaypoints(waypoints, delta) {
return waypoints.map(function(p) {
var original = p.original;
var moved = moveWaypoint(p, delta);
if (original) {
moved.original = moveWaypoint(original, delta);
}
return moved;
});
}
================================================
FILE: test/spec/features/modeling/MoveRulesSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import coreModule from 'lib/core';
import modelingModule from 'lib/features/modeling';
import moveModule from 'diagram-js/lib/features/move';
import snappingModule from 'lib/features/snapping';
import {
createCanvasEvent as canvasEvent
} from '../../../util/MockEvents';
describe('features/modeling - move', function() {
var testModules = [ coreModule, modelingModule, moveModule, snappingModule ];
var testXML = require('../../../fixtures/bpmn/boundary-events.bpmn');
beforeEach(bootstrapModeler(testXML, { modules: testModules }));
beforeEach(inject(function(dragging, canvas) {
dragging.setOptions({ manual: true });
}));
afterEach(inject(function(dragging) {
dragging.setOptions({ manual: false });
}));
it('should not attach label when moving BoundaryEvent',
inject(function(elementRegistry, move, dragging) {
// given
var boundaryEvent = elementRegistry.get('BoundaryEvent_1'),
subProcess = elementRegistry.get('SubProcess_1'),
label = boundaryEvent.label;
// when
move.start(canvasEvent({ x: 190, y: 355 }), boundaryEvent);
dragging.hover({
element: subProcess,
gfx: elementRegistry.getGraphics(subProcess)
});
dragging.move(canvasEvent({ x: 220, y: 240 }));
dragging.end();
// then
expect(subProcess.attachers).not.to.include(label);
expect(label.host).not.to.exist;
})
);
it('should move BoundaryEvent and Label with parent', inject(function(canvas, elementRegistry, move, dragging) {
// given
var boundaryEvent = elementRegistry.get('BoundaryEvent_1'),
subProcess = elementRegistry.get('SubProcess_1'),
label = boundaryEvent.label,
root = canvas.getRootElement();
// when
move.start(canvasEvent({ x: 190, y: 355 }), subProcess);
dragging.hover({
element: root,
gfx: elementRegistry.getGraphics(root)
});
dragging.move(canvasEvent({ x: 290, y: 455 }));
dragging.end();
// then
expect(subProcess.x).to.eql(304);
expect(subProcess.y).to.eql(178);
expect(subProcess.attachers).not.to.include(label);
expect(subProcess.attachers).to.include(boundaryEvent);
expect(boundaryEvent.host).to.eql(subProcess);
expect(label.host).not.to.exist;
}));
it('should move BoundaryEvent, Label and parent',
inject(function(canvas, elementRegistry, move, dragging, selection) {
// given
var boundaryEvent = elementRegistry.get('BoundaryEvent_1'),
subProcess = elementRegistry.get('SubProcess_1'),
label = boundaryEvent.label,
root = canvas.getRootElement();
// when
selection.select([ boundaryEvent, label, subProcess ]);
move.start(canvasEvent({ x: 190, y: 355 }), subProcess);
dragging.hover({
element: root,
gfx: elementRegistry.getGraphics(root)
});
dragging.move(canvasEvent({ x: 290, y: 455 }));
dragging.end();
// then
expect(subProcess.x).to.eql(304);
expect(subProcess.y).to.eql(178);
expect(subProcess.attachers).not.to.include(label);
expect(subProcess.attachers).to.include(boundaryEvent);
expect(boundaryEvent.host).to.eql(subProcess);
expect(label.host).not.to.exist;
})
);
});
================================================
FILE: test/spec/features/modeling/MoveShapeSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import modelingModule from 'lib/features/modeling';
import coreModule from 'lib/core';
import { getDi } from 'lib/util/ModelUtil';
describe('features/modeling - move shape', function() {
var diagramXML = require('../../../fixtures/bpmn/simple.bpmn');
var testModules = [ coreModule, modelingModule ];
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
describe('shape', function() {
it('should move', inject(function(elementRegistry, modeling, bpmnFactory) {
// given
var startEventElement = elementRegistry.get('StartEvent_1'),
startEventDi = getDi(startEventElement);
var sequenceFlowElement = elementRegistry.get('SequenceFlow_1'),
sequenceFlowDi = getDi(sequenceFlowElement);
var oldPosition = {
x: startEventElement.x,
y: startEventElement.y
};
// when
modeling.moveShape(startEventElement, { x: 0, y: 50 });
// then
expect(startEventDi).to.have.position({
x: oldPosition.x,
y: oldPosition.y + 50
});
var newWaypoints = sequenceFlowElement.waypoints;
var expectedDiWaypoints = bpmnFactory.createDiWaypoints(newWaypoints.map(function(p) {
return { x: p.x, y: p.y };
}));
// see LayoutSpec for actual connection layouting tests
// expect di waypoints update
expect(sequenceFlowDi.waypoint).to.eql(expectedDiWaypoints);
}));
it('should move label', inject(function(elementRegistry, modeling) {
// given
var labelElement = elementRegistry.get('StartEvent_1_label'),
startEventDi = getDi(elementRegistry.get('StartEvent_1'));
var oldPosition = {
x: labelElement.x,
y: labelElement.y
};
// when
modeling.moveShape(labelElement, { x: 0, y: 50 });
// then
expect(startEventDi.label).to.have.position({
x: oldPosition.x,
y: oldPosition.y + 50
});
}));
it('should move label to new parent', inject(function(elementRegistry, modeling) {
// given
var startEventElement = elementRegistry.get('StartEvent_1'),
labelElement = elementRegistry.get('StartEvent_1_label'),
processElement = elementRegistry.get('Process_1'),
subProcessElement = elementRegistry.get('SubProcess_1'),
startEvent = labelElement.businessObject,
subProcess = subProcessElement.businessObject;
// when
modeling.moveShape(labelElement, { x: 0, y: 50 }, processElement);
// then
expect(labelElement.parent).to.eql(processElement);
// expect actual element + businessObject to be unchanged
expect(startEventElement.parent).to.eql(subProcessElement);
expect(startEvent.$parent).to.eql(subProcess);
}));
describe('undo support', function() {
it('should undo', inject(function(elementRegistry, commandStack, modeling) {
// given
var startEvent = elementRegistry.get('StartEvent_1'),
startEventDi = getDi(startEvent);
var oldPosition = {
x: startEvent.x,
y: startEvent.y
};
modeling.moveShape(startEvent, { x: 0, y: 50 });
// when
commandStack.undo();
// then
expect(startEventDi).to.have.position(oldPosition);
}));
it('should undo on label', inject(function(elementRegistry, commandStack, modeling) {
// given
var labelElement = elementRegistry.get('StartEvent_1_label'),
startEvent = elementRegistry.get('StartEvent_1'),
startEventDi = getDi(startEvent);
var oldPosition = {
x: labelElement.x,
y: labelElement.y
};
modeling.moveShape(labelElement, { x: 0, y: 50 });
// when
commandStack.undo();
// then
expect(startEventDi.label).to.have.position(oldPosition);
}));
});
describe('redo support', function() {
it('should redo', inject(function(elementRegistry, commandStack, modeling) {
// given
var startEventElement = elementRegistry.get('StartEvent_1'),
startEventDi = getDi(startEventElement);
modeling.moveShape(startEventElement, { x: 0, y: 50 });
var newPosition = {
x: startEventElement.x,
y: startEventElement.y
};
// when
commandStack.undo();
commandStack.redo();
// then
expect(startEventDi).to.have.position(newPosition);
}));
it('should redo on label', inject(function(elementRegistry, commandStack, modeling) {
// given
var labelElement = elementRegistry.get('StartEvent_1_label'),
startEvent = elementRegistry.get('StartEvent_1'),
startEventDi = getDi(startEvent);
modeling.moveShape(labelElement, { x: 0, y: 50 });
var newPosition = {
x: labelElement.x,
y: labelElement.y
};
// when
commandStack.undo();
commandStack.redo();
// then
expect(startEventDi.label).to.have.position(newPosition);
}));
});
});
describe('label support', function() {
it('should move label with shape', inject(function(elementRegistry, modeling) {
// given
var startEventElement = elementRegistry.get('StartEvent_1');
var label = startEventElement.label;
var labelPosition = {
x: label.x,
y: label.y
};
// when
modeling.moveElements([ startEventElement ], { x: 40, y: 80 });
// then
expect(label).to.have.position({
x: labelPosition.x + 40,
y: labelPosition.y + 80
});
}));
it('should move label with connection', inject(function(elementRegistry, modeling) {
// given
var startEventElement = elementRegistry.get('StartEvent_2'),
subProcessElement = elementRegistry.get('SubProcess_1'),
flowLabel = elementRegistry.get('SequenceFlow_3_label');
var labelPosition = {
x: flowLabel.x,
y: flowLabel.y
};
// when
modeling.moveElements([ startEventElement, subProcessElement ], { x: 40, y: -80 });
// then
expect(flowLabel).to.have.position({
x: labelPosition.x + 40,
y: labelPosition.y - 80
});
}));
describe('undo', function() {
it('should undo label with shape', inject(function(elementRegistry, modeling, commandStack) {
// given
var startEventElement = elementRegistry.get('StartEvent_1');
var label = startEventElement.label;
var labelPosition = {
x: label.x,
y: label.y
};
modeling.moveElements([ startEventElement ], { x: 40, y: -80 });
// when
commandStack.undo();
// then
expect(label).to.have.position(labelPosition);
}));
it('should move label with connection', inject(function(elementRegistry, modeling, commandStack) {
// given
var startEventElement = elementRegistry.get('StartEvent_2'),
subProcessElement = elementRegistry.get('SubProcess_1'),
flowLabel = elementRegistry.get('SequenceFlow_3_label');
var labelPosition = {
x: flowLabel.x,
y: flowLabel.y
};
modeling.moveElements([ startEventElement, subProcessElement ], { x: 40, y: -80 });
// when
commandStack.undo();
// then
expect(flowLabel).to.have.position(labelPosition);
}));
});
describe('redo', function() {
it('should redo move label with shape', inject(function(elementRegistry, modeling, commandStack) {
// given
var startEventElement = elementRegistry.get('StartEvent_1');
var label = startEventElement.label;
var labelPosition = {
x: label.x,
y: label.y
};
modeling.moveElements([ startEventElement ], { x: 40, y: 80 });
commandStack.undo();
// when
commandStack.redo();
// then
expect(label).to.have.position({
x: labelPosition.x + 40,
y: labelPosition.y + 80
});
}));
it('should redo move label with connection', inject(function(elementRegistry, modeling, commandStack) {
// given
var startEventElement = elementRegistry.get('StartEvent_2'),
subProcessElement = elementRegistry.get('SubProcess_1'),
flowLabel = elementRegistry.get('SequenceFlow_3_label');
var labelPosition = {
x: flowLabel.x,
y: flowLabel.y
};
modeling.moveElements([ startEventElement, subProcessElement ], { x: 40, y: -80 });
commandStack.undo();
// when
commandStack.redo();
// then
expect(flowLabel).to.have.position({
x: labelPosition.x + 40,
y: labelPosition.y - 80
});
}));
});
});
});
================================================
FILE: test/spec/features/modeling/MoveStress.bpmn
================================================
SequenceFlow_015llny
SequenceFlow_1s8i2ri
SequenceFlow_1ww284e
SequenceFlow_13hnkxl
SequenceFlow_01uq989
SequenceFlow_1of0m15
SequenceFlow_031yqex
SequenceFlow_1wkc2h4
SequenceFlow_0exsujd
SequenceFlow_1ox345k
DataStoreReference_0lujw1y
Property_06qxa7x
DataObjectReference_06vq2e1
Property_06qxa7x
DataObjectReference_1mksk9i
Property_06qxa7x
SequenceFlow_1ww284e
SequenceFlow_1s8i2ri
SequenceFlow_0exsujd
SequenceFlow_1ox345k
SequenceFlow_015llny
SequenceFlow_1wkc2h4
SequenceFlow_1of0m15
SequenceFlow_031yqex
SequenceFlow_01uq989
SequenceFlow_13hnkxl
================================================
FILE: test/spec/features/modeling/MoveStressSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import modelingModule from 'lib/features/modeling';
import coreModule from 'lib/core';
import LoggingCroppingConnectionDocking from './LoggingCroppingConnectionDocking';
describe.skip('modeling / MoveShape - connection cropping', function() {
var diagramXML = require('./MoveStress.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: [
coreModule,
modelingModule,
{
connectionDocking: [ 'type', LoggingCroppingConnectionDocking ]
}
]
}));
var count = 0;
it('stress stress', inject(function(elementRegistry, modeling, graphicsFactory) {
var task = elementRegistry.get('TASK');
var connections = [].concat(task.incoming, task.outgoing);
function reconnect(c) {
/*
if (Math.random() > 0.9) {
console.log(
graphicsFactory.getConnectionPath(c),
graphicsFactory.getShapePath(c.source),
graphicsFactory.getShapePath(c.target)
);
};
*/
modeling[(
c.target === task
? 'reconnectEnd'
: 'reconnectStart'
)](c, task, randomDocking());
}
function randomDocking() {
return {
x: task.x + Math.round(Math.random() * (task.width)),
y: task.y + Math.round(Math.random() * (task.height))
};
}
function tick() {
setTimeout(function() {
console.log('#%s rate=%s, no-intersections=%s', count, window.noIntersectCount / count, window.noIntersectCount);
if (!window.__STOPTEST) {
tick();
}
}, 2000);
}
function next() {
setTimeout(function() {
count++;
modeling.moveElements([ task ], {
x: Math.round(Math.random() * 10 - 5),
y: Math.round(Math.random() * 10 - 5)
});
connections.forEach(function(c) {
if (Math.random() < 0.1) {
reconnect(c);
}
});
if (window.noIntersect && window.noIntersect.length) {
// reconnect all non-intersection connections
window.noIntersect.forEach(function(entry) {
reconnect(entry[0]);
});
window.noIntersect.length = 0;
}
if (!window.__STOPTEST) {
next();
}
}, 1);
}
next();
tick();
}));
});
================================================
FILE: test/spec/features/modeling/ResizeShapeSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import { pick } from 'min-dash';
import {
getDi
} from 'lib/util/ModelUtil';
import modelingModule from 'lib/features/modeling';
import coreModule from 'lib/core';
describe('features/modeling - resize shape', function() {
var diagramXML = require('../../../fixtures/bpmn/simple-resizable.bpmn');
var testModules = [ coreModule, modelingModule ];
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
describe('shape', function() {
it('should resize', inject(function(elementRegistry, modeling) {
// given
var subProcessElement = elementRegistry.get('SubProcess_1'),
originalWidth = subProcessElement.width;
// when
modeling.resizeShape(subProcessElement, { x: 339, y: 142, width: 250, height: 200 });
// then
expect(subProcessElement.width).to.equal(250);
expect(subProcessElement.width).to.not.equal(originalWidth);
}));
describe('businessObject', function() {
it('should update bounds', inject(function(elementRegistry, modeling) {
// given
var subProcessElement = elementRegistry.get('SubProcess_1');
// when
modeling.resizeShape(subProcessElement, { x: 339, y: 142, width: 250, height: 200 });
// then
var di = getDi(subProcessElement);
expect(di.bounds.width).to.equal(250);
}));
it('should update group bounds', inject(function(elementRegistry, modeling) {
// given
var subProcessElement = elementRegistry.get('Group_1');
// when
modeling.resizeShape(subProcessElement, { x: 250, y: 250, width: 550, height: 400 });
// then
var di = getDi(subProcessElement);
expect(di.bounds.width).to.equal(550);
}));
});
describe('connected flow', function() {
it('should resize', inject(function(elementRegistry, modeling, bpmnFactory) {
// given
var subProcessElement = elementRegistry.get('SubProcess_1');
var sequenceFlowElement = elementRegistry.get('SequenceFlow_2'),
sequenceFlowDi = getDi(sequenceFlowElement);
// when
// Decreasing width by 100px
modeling.resizeShape(subProcessElement, { x: 339, y: 142, width: 250, height: 200 });
// then
// expect flow layout
var diWaypoints = bpmnFactory.createDiWaypoints([
{ x: 589, y: 242 },
{ x: 821, y: 242 }
]);
expect(sequenceFlowDi.waypoint).eql(diWaypoints);
}));
it('should move', inject(function(elementRegistry, modeling, bpmnFactory) {
// given
var subProcessElement = elementRegistry.get('SubProcess_1');
var sequenceFlowElement = elementRegistry.get('SequenceFlow_2'),
sequenceFlowDi = getDi(sequenceFlowElement);
// when
modeling.moveShape(subProcessElement, { x: -50, y: 0 });
// then
// expect flow layout
var diWaypoints = bpmnFactory.createDiWaypoints([
{ x: 639, y: 242 },
{ x: 821, y: 242 }
]);
expect(sequenceFlowDi.waypoint).eql(diWaypoints);
}));
});
});
describe('integration', function() {
var diagramXML = require('../../../fixtures/bpmn/boundary-events.bpmn');
var testModules = [ coreModule, modelingModule ];
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
it('should not move Boundary Event if unnecessary', inject(function(elementRegistry, modeling) {
// given
var boundaryEvent = elementRegistry.get('BoundaryEvent_3'),
originalPosition = getPosition(boundaryEvent),
subProcessElement = elementRegistry.get('SubProcess_1');
// when
modeling.resizeShape(subProcessElement, { x: 204, y: 28, width: 400, height: 339 });
// then
expect(getPosition(boundaryEvent)).to.jsonEqual(originalPosition);
}));
});
});
// helper /////
function getPosition(shape) {
return pick(shape, [ 'x', 'y' ]);
}
================================================
FILE: test/spec/features/modeling/SetColor.bpmn
================================================
SequenceFlow_3
SequenceFlow_2
SequenceFlow_1
SequenceFlow_1
SequenceFlow_2
SequenceFlow_3
================================================
FILE: test/spec/features/modeling/SetColorSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import { getDi } from 'lib/util/ModelUtil';
import modelingModule from 'lib/features/modeling';
import coreModule from 'lib/core';
var FUCHSIA_HEX = '#ff00ff',
YELLOW_HEX = '#ffff00';
describe('features/modeling - set color', function() {
var diagramXML = require('./SetColor.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: [
coreModule,
modelingModule
]
}));
describe('execute', function() {
it('setting fill color', inject(function(elementRegistry, modeling) {
// given
var taskShape = elementRegistry.get('Task_1'),
taskDi = getDi(taskShape);
// when
modeling.setColor(taskShape, { fill: 'FUCHSIA' });
// then
expect(taskDi.get('background-color')).to.equal(FUCHSIA_HEX);
}));
it('unsetting fill color', inject(function(elementRegistry, modeling) {
// given
var taskShape = elementRegistry.get('Task_1'),
taskDi = getDi(taskShape);
modeling.setColor(taskShape, { fill: 'FUCHSIA' });
// when
modeling.setColor(taskShape);
// then
expect(taskDi.get('background-color')).not.to.exist;
}));
it('setting fill color without changing stroke color', inject(
function(elementRegistry, modeling) {
// given
var taskShape = elementRegistry.get('Task_1'),
taskDi = getDi(taskShape);
modeling.setColor(taskShape, { fill: 'FUCHSIA', stroke: 'YELLOW' });
// when
modeling.setColor(taskShape, { fill: 'YELLOW' });
// then
expect(taskDi.get('background-color')).to.equal(YELLOW_HEX);
expect(taskDi.get('border-color')).to.equal(YELLOW_HEX);
}
));
it('unsetting fill color without changing stroke color', inject(
function(elementRegistry, modeling) {
// given
var taskShape = elementRegistry.get('Task_1'),
taskDi = getDi(taskShape);
modeling.setColor(taskShape, { fill: 'FUCHSIA', stroke: 'YELLOW' });
// when
modeling.setColor(taskShape, { fill: undefined });
// then
expect(taskDi.get('background-color')).not.to.exist;
expect(taskDi.get('border-color')).to.equal(YELLOW_HEX);
}
));
it('unsetting both fill and stroke color', inject(
function(elementRegistry, modeling) {
// given
var taskShape = elementRegistry.get('Task_1'),
taskDi = getDi(taskShape);
modeling.setColor(taskShape, { fill: 'FUCHSIA' });
// when
modeling.setColor(taskShape);
// then
expect(taskDi.get('background-color')).not.to.exist;
expect(taskDi.get('border-color')).not.to.exist;
}
));
it('setting stroke color', inject(function(elementRegistry, modeling) {
// given
var taskShape = elementRegistry.get('Task_1'),
taskDi = getDi(taskShape);
// when
modeling.setColor(taskShape, { stroke: 'FUCHSIA' });
// then
expect(taskDi.get('border-color')).to.equal(FUCHSIA_HEX);
expect(taskDi.get('background-color')).not.to.exist;
}));
it('unsetting stroke color', inject(function(elementRegistry, modeling) {
// given
var taskShape = elementRegistry.get('Task_1'),
taskDi = getDi(taskShape);
modeling.setColor(taskShape, { stroke: 'FUCHSIA' });
// when
modeling.setColor(taskShape);
// then
expect(taskDi.get('border-color')).not.to.exist;
expect(taskDi.get('background-color')).not.to.exist;
}));
it('setting stroke + fill color on external label', inject(function(elementRegistry, modeling) {
// given
var flowShape = elementRegistry.get('SequenceFlow_3'),
flowLabel = flowShape.label,
flowDi = getDi(flowShape);
// when
modeling.setColor(flowLabel, { stroke: 'YELLOW', fill: 'FUCHSIA' });
// then
expect(flowDi.get('border-color')).not.to.exist;
expect(flowDi.get('background-color')).not.to.exist;
expect(flowDi.label.get('color')).to.eql(YELLOW_HEX);
}));
it('unsetting stroke + fill color on external label', inject(function(elementRegistry, modeling) {
// given
var flowShape = elementRegistry.get('SequenceFlow_3'),
flowLabel = flowShape.label,
flowDi = getDi(flowShape);
// assume
modeling.setColor(flowLabel, { stroke: 'FUCHSIA', fill: 'FUCHSIA' });
// when
modeling.setColor(flowLabel, { stroke: undefined, fill: undefined });
// then
expect(flowDi.get('border-color')).not.to.exist;
expect(flowDi.get('background-color')).not.to.exist;
expect(flowDi.label.get('color')).not.to.exist;
}));
it('setting fill color (multiple elements)', inject(
function(elementRegistry, modeling) {
// given
var taskShape = elementRegistry.get('Task_1'),
taskDi = getDi(taskShape),
startEventShape = elementRegistry.get('StartEvent_1'),
startEventDi = getDi(startEventShape);
// when
modeling.setColor([ taskShape, startEventShape ], { fill: 'FUCHSIA' });
// then
expect(taskDi.get('background-color')).to.equal(FUCHSIA_HEX);
expect(startEventDi.get('background-color')).to.equal(FUCHSIA_HEX);
expect(taskDi.get('border-color')).not.to.exist;
expect(startEventDi.get('border-color')).not.to.exist;
}
));
it('unsetting fill color (multiple elements)', inject(
function(elementRegistry, modeling) {
// given
var taskShape = elementRegistry.get('Task_1'),
taskDi = getDi(taskShape),
startEventShape = elementRegistry.get('StartEvent_1'),
startEventDi = getDi(startEventShape);
modeling.setColor([ taskShape, startEventShape ], { fill: 'FUCHSIA' });
// when
modeling.setColor([ taskShape, startEventShape ]);
// then
expect(taskDi.get('background-color')).not.to.exist;
expect(startEventDi.get('background-color')).not.to.exist;
}
));
it('setting stroke color (multiple elements)', inject(
function(elementRegistry, modeling) {
// given
var taskShape = elementRegistry.get('Task_1'),
taskDi = getDi(taskShape),
startEventShape = elementRegistry.get('StartEvent_1'),
startEventDi = getDi(startEventShape);
// when
modeling.setColor([
taskShape,
startEventShape
], { stroke: 'FUCHSIA' });
// then
expect(taskDi.get('border-color')).to.equal(FUCHSIA_HEX);
expect(startEventDi.get('border-color')).to.equal(FUCHSIA_HEX);
expect(taskDi.get('background-color')).not.to.exist;
expect(startEventDi.get('background-color')).not.to.exist;
}
));
it('unsetting stroke color (multiple elements)', inject(
function(elementRegistry, modeling) {
// given
var taskShape = elementRegistry.get('Task_1'),
taskDi = getDi(taskShape),
startEventShape = elementRegistry.get('StartEvent_1'),
startEventDi = getDi(startEventShape);
modeling.setColor([
taskShape,
startEventShape
], { stroke: 'FUCHSIA' });
// when
modeling.setColor([ taskShape, startEventShape ]);
// then
expect(taskDi.get('border-color')).not.to.exist;
expect(startEventDi.get('border-color')).not.to.exist;
}
));
it('should not set background-color on BPMNEdge', inject(function(elementRegistry, modeling) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow_1'),
sequenceFlowDi = getDi(sequenceFlow);
// when
modeling.setColor(sequenceFlow, { fill: 'FUCHSIA' });
// then
expect(sequenceFlowDi.get('background-color')).not.to.exist;
}));
it('should throw for an invalid color', inject(function(elementRegistry, modeling) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow_1');
// when
function setColor() {
modeling.setColor(sequenceFlow, { fill: 'INVALID_COLOR' });
}
// then
expect(setColor).to.throw(/^invalid color value/);
}));
it('should throw for a color with alpha', inject(function(elementRegistry, modeling) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow_1');
// when
function setColor() {
modeling.setColor(sequenceFlow, { fill: 'rgba(0, 255, 0, 0.5)' });
}
// then
expect(setColor).to.throw(/^invalid color value/);
}));
it('should ignore BPMNPlane (Process)', inject(function(elementRegistry, modeling) {
// given
var processElement = elementRegistry.get('Process_1'),
processDi = getDi(processElement);
// when
modeling.setColor(processElement, { fill: '#abcdef' });
// then
expect(processDi.get('background-color')).not.to.exist;
}));
it('should ignore BPMNPlane (SubProcess)', inject(function(canvas, elementRegistry, modeling) {
// given
canvas.setRootElement(canvas.findRoot('Collapsed_plane'));
var subprocess = canvas.getRootElement(),
subprocessDi = getDi(subprocess);
// when
modeling.setColor(subprocess, { fill: '#abcdef' });
// then
expect(subprocessDi.get('background-color')).not.to.exist;
}));
});
describe('undo', function() {
it('setting fill color', inject(
function(elementRegistry, commandStack, modeling) {
// given
var taskShape = elementRegistry.get('Task_1'),
taskDi = getDi(taskShape);
// when
modeling.setColor(taskShape, { fill: 'FUCHSIA' });
commandStack.undo();
// then
expect(taskDi.get('background-color')).not.to.exist;
}
));
it('unsetting fill color', inject(
function(elementRegistry, commandStack, modeling) {
// given
var taskShape = elementRegistry.get('Task_1'),
taskDi = getDi(taskShape);
modeling.setColor(taskShape, { fill: 'FUCHSIA' });
// when
modeling.setColor(taskShape);
commandStack.undo();
// then
expect(taskDi.get('background-color')).to.equal(FUCHSIA_HEX);
}
));
it('setting stroke color', inject(
function(elementRegistry, commandStack, modeling) {
// given
var taskShape = elementRegistry.get('Task_1'),
taskDi = getDi(taskShape);
// when
modeling.setColor(taskShape, { stroke: 'FUCHSIA' });
commandStack.undo();
// then
expect(taskDi.get('border-color')).not.to.exist;
}
));
it('unsetting stroke color', inject(
function(elementRegistry, commandStack, modeling) {
// given
var taskShape = elementRegistry.get('Task_1'),
taskDi = getDi(taskShape);
modeling.setColor(taskShape, { stroke: 'FUCHSIA' });
// when
modeling.setColor(taskShape);
commandStack.undo();
// then
expect(taskDi.get('border-color')).to.equal(FUCHSIA_HEX);
}
));
it('setting fill color (multiple elements)', inject(
function(elementRegistry, commandStack, modeling) {
// given
var taskShape = elementRegistry.get('Task_1'),
taskDi = getDi(taskShape),
startEventShape = elementRegistry.get('StartEvent_1'),
startEventDi = getDi(taskShape);
// when
modeling.setColor([ taskShape, startEventShape ], { fill: 'FUCHSIA' });
commandStack.undo();
// then
expect(taskDi.get('background-color')).not.to.exist;
expect(startEventDi.get('background-color')).not.to.exist;
}
));
it('unsetting fill color (multiple elements)', inject(
function(elementRegistry, commandStack, modeling) {
// given
var taskShape = elementRegistry.get('Task_1'),
taskDi = getDi(taskShape),
startEventShape = elementRegistry.get('StartEvent_1'),
startEventDi = getDi(taskShape);
modeling.setColor([ taskShape, startEventShape ], { fill: 'FUCHSIA' });
// when
modeling.setColor([ taskShape, startEventShape ]);
commandStack.undo();
// then
expect(taskDi.get('background-color')).to.equal(FUCHSIA_HEX);
expect(startEventDi.get('background-color')).to.equal(FUCHSIA_HEX);
}
));
it('setting stroke color (multiple elements)', inject(
function(elementRegistry, commandStack, modeling) {
// given
var taskShape = elementRegistry.get('Task_1'),
taskDi = getDi(taskShape),
startEventShape = elementRegistry.get('StartEvent_1'),
startEventDi = getDi(taskShape);
// when
modeling.setColor([
taskShape,
startEventShape
], { stroke: 'FUCHSIA' });
commandStack.undo();
// then
expect(taskDi.get('border-color')).not.to.exist;
expect(startEventDi.get('border-color')).not.to.exist;
}
));
it('unsetting stroke color (multiple elements)', inject(
function(elementRegistry, commandStack, modeling) {
// given
var taskShape = elementRegistry.get('Task_1'),
taskDi = getDi(taskShape),
startEventShape = elementRegistry.get('StartEvent_1'),
startEventDi = getDi(taskShape);
modeling.setColor([ taskShape, startEventShape ], { stroke: 'FUCHSIA' });
// when
modeling.setColor([ taskShape, startEventShape ]);
commandStack.undo();
// then
expect(taskDi.get('border-color')).to.equal(FUCHSIA_HEX);
expect(startEventDi.get('border-color')).to.equal(FUCHSIA_HEX);
}
));
});
describe('redo', function() {
it('setting fill color', inject(
function(elementRegistry, commandStack, modeling) {
// given
var taskShape = elementRegistry.get('Task_1'),
taskDi = getDi(taskShape);
// when
modeling.setColor(taskShape, { fill: 'FUCHSIA' });
commandStack.undo();
commandStack.redo();
// then
expect(taskDi.get('background-color')).to.equal(FUCHSIA_HEX);
}
));
it('unsetting fill color', inject(
function(elementRegistry, commandStack, modeling) {
// given
var taskShape = elementRegistry.get('Task_1'),
taskDi = getDi(taskShape);
modeling.setColor(taskShape, { fill: 'FUCHSIA' });
// when
modeling.setColor(taskShape);
commandStack.undo();
commandStack.redo();
// then
expect(taskDi.get('background-color')).not.to.exist;
}
));
it('setting stroke color', inject(
function(elementRegistry, commandStack, modeling) {
// given
var taskShape = elementRegistry.get('Task_1'),
taskDi = getDi(taskShape);
// when
modeling.setColor(taskShape, { stroke: 'FUCHSIA' });
commandStack.undo();
commandStack.redo();
// then
expect(taskDi.get('border-color')).to.equal(FUCHSIA_HEX);
}
));
it('unsetting stroke color', inject(
function(elementRegistry, commandStack, modeling) {
// given
var taskShape = elementRegistry.get('Task_1'),
taskDi = getDi(taskShape);
modeling.setColor(taskShape, { stroke: 'FUCHSIA' });
// when
modeling.setColor(taskShape);
commandStack.undo();
commandStack.redo();
// then
expect(taskDi.get('border-color')).not.to.exist;
}
));
it('setting fill color (multiple elements)', inject(
function(elementRegistry, commandStack, modeling) {
// given
var taskShape = elementRegistry.get('Task_1'),
taskDi = getDi(taskShape),
startEventShape = elementRegistry.get('StartEvent_1'),
startEventDi = getDi(taskShape);
// when
modeling.setColor([ taskShape, startEventShape ], { fill: 'FUCHSIA' });
commandStack.undo();
commandStack.redo();
// then
expect(taskDi.get('background-color')).to.equal(FUCHSIA_HEX);
expect(startEventDi.get('background-color')).to.equal(FUCHSIA_HEX);
}
));
it('unsetting fill color (multiple elements)', inject(
function(elementRegistry, commandStack, modeling) {
// given
var taskShape = elementRegistry.get('Task_1'),
taskDi = getDi(taskShape),
startEventShape = elementRegistry.get('StartEvent_1'),
startEventDi = getDi(taskShape);
modeling.setColor([ taskShape, startEventShape ], { fill: 'FUCHSIA' });
// when
modeling.setColor([ taskShape, startEventShape ]);
commandStack.undo();
commandStack.redo();
// then
expect(taskDi.get('background-color')).not.to.exist;
expect(startEventDi.get('background-color')).not.to.exist;
}
));
it('setting stroke color (multiple elements)', inject(
function(elementRegistry, commandStack, modeling) {
// given
var taskShape = elementRegistry.get('Task_1'),
taskDi = getDi(taskShape),
startEventShape = elementRegistry.get('StartEvent_1'),
startEventDi = getDi(taskShape);
// when
modeling.setColor([ taskShape, startEventShape ], { stroke: 'FUCHSIA' });
commandStack.undo();
commandStack.redo();
// then
expect(taskDi.get('border-color')).to.equal(FUCHSIA_HEX);
expect(startEventDi.get('border-color')).to.equal(FUCHSIA_HEX);
}
));
it('unsetting stroke color (multiple elements)', inject(
function(elementRegistry, commandStack, modeling) {
// given
var taskShape = elementRegistry.get('Task_1'),
taskDi = getDi(taskShape),
startEventShape = elementRegistry.get('StartEvent_1'),
startEventDi = getDi(taskShape);
modeling.setColor([
taskShape,
startEventShape
], { stroke: 'FUCHSIA' });
// when
modeling.setColor([ taskShape, startEventShape ]);
commandStack.undo();
commandStack.redo();
// then
expect(taskDi.get('border-color')).not.to.exist;
expect(startEventDi.get('border-color')).not.to.exist;
}
));
});
// TODO @barmac: remove once we drop bpmn.io properties
describe('legacy', function() {
it('should set both BPMN in Color and bpmn.io properties on BPMNShape', inject(
function(elementRegistry, modeling) {
// given
var taskShape = elementRegistry.get('Task_1'),
taskDi = getDi(taskShape);
// when
modeling.setColor(taskShape, { stroke: '#abcdef', fill: '#fedcba' });
// then
expect(taskDi.get('border-color')).to.eql('#abcdef');
expect(taskDi.get('stroke')).to.eql('#abcdef');
expect(taskDi.get('background-color')).to.eql('#fedcba');
expect(taskDi.get('fill')).to.eql('#fedcba');
}
));
it('should set both BPMN in Color and bpmn.io properties on BPMNEdge', inject(
function(elementRegistry, modeling) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow_1'),
sequenceFlowDi = getDi(sequenceFlow);
// when
modeling.setColor(sequenceFlow, { stroke: '#abcdef' });
// then
expect(sequenceFlowDi.get('border-color')).to.eql('#abcdef');
expect(sequenceFlowDi.get('stroke')).to.eql('#abcdef');
}
));
});
});
================================================
FILE: test/spec/features/modeling/UpdateAttachmentSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import modelingModule from 'lib/features/modeling';
import coreModule from 'lib/core';
describe('features/modeling - update attachment', function() {
var diagramXML = require('../../../fixtures/bpmn/boundary-events.bpmn');
var testModules = [ coreModule, modelingModule ];
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
var subProcessElement, subProcess, task, boundaryEventElement, boundaryEvent;
beforeEach(inject(function(elementFactory, elementRegistry, canvas, modeling) {
task = elementRegistry.get('Task_1');
subProcessElement = elementRegistry.get('SubProcess_1');
subProcess = subProcessElement.businessObject;
boundaryEventElement = elementFactory.createShape({
type: 'bpmn:BoundaryEvent',
host: task,
x: 513, y: 254
});
boundaryEvent = boundaryEventElement.businessObject;
canvas.addShape(boundaryEventElement, subProcessElement);
}));
describe('should reattach BoundaryEvent', function() {
it('execute', inject(function(modeling, elementRegistry) {
// when
modeling.updateAttachment(boundaryEventElement, subProcessElement);
// then
expect(boundaryEvent.attachedToRef).to.equal(subProcess);
expect(boundaryEvent.cancelActivity).to.equal(true);
expect(subProcessElement.attachers).to.include(boundaryEventElement);
expect(task.attachers).not.to.include(boundaryEventElement);
}));
it('undo', inject(function(elementRegistry, commandStack, modeling) {
// given
modeling.updateAttachment(boundaryEventElement, subProcessElement);
// when
commandStack.undo();
// then
expect(boundaryEvent.attachedToRef).to.equal(task.businessObject);
expect(boundaryEvent.cancelActivity).to.equal(true);
expect(subProcessElement.attachers).not.to.include(boundaryEventElement);
expect(task.attachers).to.include(boundaryEventElement);
}));
it('redo', inject(function(elementRegistry, commandStack, modeling) {
// given
modeling.updateAttachment(boundaryEventElement, subProcessElement);
// when
commandStack.undo();
commandStack.redo();
// then
expect(boundaryEvent.attachedToRef).to.equal(subProcess);
expect(boundaryEvent.cancelActivity).to.equal(true);
expect(subProcessElement.attachers).to.include(boundaryEventElement);
expect(task.attachers).not.to.include(boundaryEventElement);
}));
});
});
================================================
FILE: test/spec/features/modeling/UpdateLabel.bpmn
================================================
================================================
FILE: test/spec/features/modeling/UpdateLabelSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import modelingModule from 'lib/features/modeling';
import coreModule from 'lib/core';
describe('features/modeling - update label', function() {
var diagramXML = require('./UpdateLabel.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: [
coreModule,
modelingModule
]
}));
it('should change name of start event', inject(
function(modeling, elementRegistry) {
// given
var startEvent_1 = elementRegistry.get('StartEvent_1');
var businessObject = startEvent_1.businessObject;
// when
modeling.updateLabel(startEvent_1, 'bar');
// then
expect(businessObject.name).to.equal('bar');
}
));
it('should create label name of start event', inject(
function(modeling, elementRegistry) {
// given
var startEvent_2 = elementRegistry.get('StartEvent_2');
var businessObject = startEvent_2.businessObject;
// when
modeling.updateLabel(startEvent_2, 'bar');
// then
var label = startEvent_2.label;
var di = startEvent_2.di;
expect(businessObject.name).to.equal('bar');
expect(label).to.exist;
expect(label.businessObject).to.equal(businessObject);
expect(label.di).to.equal(di);
}
));
it('should not create label on empty text', inject(
function(modeling, elementRegistry) {
// given
var startEvent_2 = elementRegistry.get('StartEvent_2');
// when
modeling.updateLabel(startEvent_2, '');
// then
expect(startEvent_2.businessObject.name).to.equal('');
expect(startEvent_2.label).not.to.exist;
expect(startEvent_2).to.have.dimensions({
width: 36,
height: 36
});
}
));
it('should not create label on (sub)process plane', inject(
function(modeling, elementRegistry) {
// given
var SubProcess_1 = elementRegistry.get('Subprocess_1_plane');
// when
modeling.updateLabel(SubProcess_1, 'Cool new label');
// then
expect(SubProcess_1.businessObject.name).to.equal('Cool new label');
expect(SubProcess_1.di.get('label')).not.to.exist;
}
));
describe('should delete label', function() {
it('when setting null', inject(
function(modeling, elementRegistry) {
// given
var startEvent_1 = elementRegistry.get('StartEvent_1');
// when
modeling.updateLabel(startEvent_1, null);
// then
expect(startEvent_1.businessObject.name).not.to.exist;
expect(startEvent_1.label).not.to.exist;
}
));
it('when setting empty string', inject(
function(modeling, elementRegistry) {
// given
var startEvent_1 = elementRegistry.get('StartEvent_1');
// when
modeling.updateLabel(startEvent_1, '');
// then
expect(startEvent_1.businessObject.name).to.equal('');
expect(startEvent_1.label).not.to.exist;
}
));
});
it('should change name of start event when editing label', inject(
function(modeling, elementRegistry) {
// given
var startEvent_1 = elementRegistry.get('StartEvent_1');
var startEvent_1_label = elementRegistry.get('StartEvent_1_label');
// when
modeling.updateLabel(startEvent_1_label, 'bar');
// then
expect(startEvent_1.businessObject.name).to.equal('bar');
}
));
it('should change text annotation text and bounds', inject(
function(modeling, elementRegistry) {
// given
var element = elementRegistry.get('TextAnnotation_1');
var newBounds = { x: 100, y: 100, width: 100, height: 30 };
// when
modeling.updateLabel(element, 'bar', newBounds);
// then
expect(element.businessObject.text).to.equal('bar');
expect(element).to.have.bounds(newBounds);
}
));
it('should update group label', inject(function(modeling, elementRegistry) {
// given
var group = elementRegistry.get('Group_1');
// when
modeling.updateLabel(group, 'bar');
// then
expect(group.businessObject.categoryValueRef.value).to.equal('bar');
expect(group.label).to.exist;
}));
it('should create group label', inject(function(modeling, elementRegistry) {
// given
var group = elementRegistry.get('Group_2');
// when
modeling.updateLabel(group, 'foo');
// then
expect(group.businessObject.categoryValueRef.value).to.equal('foo');
expect(group.label).to.exist;
}));
it('should not create group label on empty text', inject(function(modeling, elementRegistry) {
// given
var group = elementRegistry.get('Group_2');
// when
modeling.updateLabel(group, null);
// then
expect(group.businessObject.categoryValueRef).to.not.exist;
expect(group.label).to.not.exist;
}));
it('should properly fire events.changed after event name change', inject(
function(modeling, elementRegistry, eventBus) {
// given
var startEvent_1 = elementRegistry.get('StartEvent_1');
var changedEvent;
eventBus.on('elements.changed', function(event) {
changedEvent = event;
});
// when
modeling.updateLabel(startEvent_1, 'foo');
// then
expect(changedEvent.elements).to.include(startEvent_1);
}
));
it('should propertly fire events.changed after event label change', inject(
function(modeling, elementRegistry, eventBus) {
// given
var startEvent_1 = elementRegistry.get('StartEvent_1');
var startEvent_1_label = elementRegistry.get('StartEvent_1_label');
var changedEvent;
eventBus.on('elements.changed', function(event) {
changedEvent = event;
});
// when
modeling.updateLabel(startEvent_1_label, 'foo');
// then
expect(changedEvent.elements).to.include(startEvent_1);
}
));
it('should resize empty text annotation', inject(function(modeling, elementRegistry) {
// given
var element = elementRegistry.get('TextAnnotation_1');
var newBounds = { x: 100, y: 100, width: 100, height: 30 };
// when
modeling.updateLabel(element, null, newBounds);
// then
expect(element).to.have.bounds(newBounds);
}));
describe('embedded labels', function() {
it('should change name of task', inject(function(modeling, elementRegistry) {
// given
var task_1 = elementRegistry.get('Task_1');
// when
modeling.updateLabel(task_1, 'foo');
// then
expect(task_1.businessObject.name).to.equal('foo');
expect(task_1.di.label).to.exist;
}));
it('should delete label of task', inject(function(modeling, elementRegistry) {
// given
var task_2 = elementRegistry.get('Task_2');
// when
modeling.updateLabel(task_2, '');
// then
expect(task_2.businessObject.name).to.equal('');
expect(task_2.di).not.to.have.property('label');
}));
});
});
================================================
FILE: test/spec/features/modeling/UpdateModdleProperties.dataObject.bpmn
================================================
================================================
FILE: test/spec/features/modeling/UpdateModdleProperties.error.bpmn
================================================
================================================
FILE: test/spec/features/modeling/UpdateModdlePropertiesSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import modelingModule from 'lib/features/modeling';
import coreModule from 'lib/core';
var testModules = [ coreModule, modelingModule ];
describe('features/modeling - update moddle properties', function() {
describe('updating bpmn:Error', function() {
var diagramXML = require('./UpdateModdleProperties.error.bpmn');
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
it('should update', inject(function(elementRegistry, modeling, eventBus) {
// given
var eventShape = elementRegistry.get('StartEvent_1');
var error = eventShape.businessObject.eventDefinitions[0].errorRef;
var changedElements;
var elementsChangedListener = sinon.spy(function(event) {
changedElements = event.elements;
});
eventBus.on('elements.changed', elementsChangedListener);
// assume
expect(error.name).to.eql('Special Error');
// when
modeling.updateModdleProperties(eventShape, error, { name: 'Other Error' });
// then
// updated data object
expect(error.name).to.eql('Other Error');
// changed affected elements
expect(changedElements).to.eql([
eventShape
]);
}));
});
describe('updating bpmn:DataObject', function() {
var diagramXML = require('./UpdateModdleProperties.dataObject.bpmn');
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
it('should update', inject(function(elementRegistry, modeling, eventBus) {
// given
var dataObjectReference1 = elementRegistry.get('DataObjectReference_1');
var dataObjectReference2 = elementRegistry.get('DataObjectReference_2');
var dataObject = dataObjectReference1.businessObject.dataObjectRef;
var changedElements;
var elementsChangedListener = sinon.spy(function(event) {
changedElements = event.elements;
});
eventBus.on('elements.changed', elementsChangedListener);
// assume
expect(dataObject).to.eql(dataObjectReference2.businessObject.dataObjectRef);
// when
modeling.updateModdleProperties(dataObjectReference1, dataObject, { isCollection: true });
// then
// updated data object
expect(dataObject.isCollection).to.be.true;
// changed affected elements
expect(changedElements).to.eql([
dataObjectReference1,
dataObjectReference2
]);
}));
it('should undo', inject(function(commandStack, elementRegistry, eventBus, modeling) {
// given
var dataObjectReference1 = elementRegistry.get('DataObjectReference_1');
var dataObjectReference2 = elementRegistry.get('DataObjectReference_2');
var dataObject = dataObjectReference1.businessObject.dataObjectRef;
var changedElements;
var elementsChangedListener = sinon.spy(function(event) {
changedElements = event.elements;
});
modeling.updateModdleProperties(dataObjectReference1, dataObject, { isCollection: true });
eventBus.on('elements.changed', elementsChangedListener);
// when
commandStack.undo();
// then
// updated data object
expect(dataObject.isCollection).to.be.false;
// changed affected elements
expect(changedElements).to.eql([
dataObjectReference1,
dataObjectReference2
]);
}));
it('should redo', inject(function(commandStack, elementRegistry, eventBus, modeling) {
// given
var dataObjectReference1 = elementRegistry.get('DataObjectReference_1');
var dataObjectReference2 = elementRegistry.get('DataObjectReference_2');
var dataObject = dataObjectReference1.businessObject.dataObjectRef;
var changedElements;
var elementsChangedListener = sinon.spy(function(event) {
changedElements = event.elements;
});
modeling.updateModdleProperties(dataObjectReference1, dataObject, { isCollection: true });
commandStack.undo();
eventBus.on('elements.changed', elementsChangedListener);
// when
commandStack.redo();
// then
// updated data object
expect(dataObject.isCollection).to.be.true;
// changed affected elements
expect(changedElements).to.eql([
dataObjectReference1,
dataObjectReference2
]);
}));
});
});
================================================
FILE: test/spec/features/modeling/UpdatePropertiesSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import { getDi } from 'lib/util/ModelUtil';
import modelingModule from 'lib/features/modeling';
import coreModule from 'lib/core';
describe('features/modeling - update properties', function() {
var diagramXML = require('../../../fixtures/bpmn/conditions.bpmn');
var testModules = [ coreModule, modelingModule ];
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
var updatedElements;
beforeEach(inject(function(eventBus) {
eventBus.on([ 'commandStack.execute', 'commandStack.revert' ], function() {
updatedElements = [];
});
eventBus.on('element.changed', function(event) {
updatedElements.push(event.element);
});
}));
describe('should execute', function() {
it('setting loop characteristics', inject(
function(elementRegistry, modeling, moddle) {
// given
var loopCharacteristics = moddle.create(
'bpmn:MultiInstanceLoopCharacteristics'
);
var taskShape = elementRegistry.get('ServiceTask_1');
// when
modeling.updateProperties(taskShape, {
loopCharacteristics: loopCharacteristics
});
// then
expect(
taskShape.businessObject.loopCharacteristics
).to.eql(loopCharacteristics);
// task shape got updated
expect(updatedElements).to.include(taskShape);
}
));
it('unsetting default flow', inject(function(elementRegistry, modeling) {
// given
var gatewayShape = elementRegistry.get('ExclusiveGateway_1');
// when
modeling.updateProperties(gatewayShape, { 'default': undefined });
// then
expect(gatewayShape.businessObject['default']).not.to.exist;
// flow got updated, too
expect(updatedElements).to.include(elementRegistry.get('SequenceFlow_1'));
}));
it('updating default flow', inject(function(elementRegistry, modeling) {
// given
var gatewayShape = elementRegistry.get('ExclusiveGateway_1'),
newDefaultFlowConnection = elementRegistry.get('SequenceFlow_2'),
newDefaultFlow = newDefaultFlowConnection.businessObject;
// when
modeling.updateProperties(gatewayShape, { 'default': newDefaultFlow });
// then
expect(gatewayShape.businessObject['default']).to.eql(newDefaultFlow);
// flow got updated, too
expect(updatedElements).to.include(newDefaultFlowConnection);
}));
it('should keep unchanged default flow intact', inject(
function(elementRegistry, modeling) {
// given
var gatewayShape = elementRegistry.get('ExclusiveGateway_1'),
sequenceFlow = elementRegistry.get('SequenceFlow_2'),
taskShape = elementRegistry.get('Task_1');
// when
modeling.reconnectStart(
sequenceFlow,
taskShape,
{
x: taskShape.x + taskShape.width,
y: taskShape.y + taskShape.height / 2
}
);
// then
expect(gatewayShape.businessObject.default).to.exist;
}
));
it('updating conditional flow on source replace', inject(
function(bpmnReplace, elementRegistry) {
// given
var conditionalFlow = elementRegistry.get('SequenceFlow_3'),
conditionalBo = conditionalFlow.businessObject,
serviceTask = elementRegistry.get('ServiceTask_1');
var conditionExpression = conditionalBo.conditionExpression;
var userTaskData = {
type: 'bpmn:UserTask'
};
// when
bpmnReplace.replaceElement(serviceTask, userTaskData);
// then
expect(conditionalBo.conditionExpression).to.eql(conditionExpression);
}
));
it('updating conditional flow on target replace', inject(
function(bpmnReplace, elementRegistry) {
// given
var conditionalFlow = elementRegistry.get('SequenceFlow_3'),
conditionalBo = conditionalFlow.businessObject,
endEvent = elementRegistry.get('EndEvent_1');
var conditionExpression = conditionalBo.conditionExpression;
var messageEndEventData = {
type: 'bpmn:EndEvent',
eventDefinitionType: 'bpmn:MessageEventDefinition'
};
// when
bpmnReplace.replaceElement(endEvent, messageEndEventData);
// then
expect(conditionalBo.conditionExpression).to.eql(conditionExpression);
}
));
it('updating name', inject(function(elementRegistry, modeling) {
// given
var flowConnection = elementRegistry.get('SequenceFlow_1');
// when
modeling.updateProperties(flowConnection, { name: 'FOO BAR' });
// then
expect(flowConnection.businessObject.name).to.equal('FOO BAR');
// flow label got updated, too
expect(updatedElements).to.include(flowConnection.label);
}));
it('unsetting name', inject(function(elementRegistry, modeling) {
// given
var flowConnection = elementRegistry.get('SequenceFlow_3');
// when
modeling.updateProperties(flowConnection, { name: undefined });
// then
expect(flowConnection.businessObject.name).not.to.exist;
}));
it('updating id', inject(function(elementRegistry, modeling) {
// given
var flowConnection = elementRegistry.get('SequenceFlow_1'),
flowBo = flowConnection.businessObject;
var ids = flowBo.$model.ids;
// when
modeling.updateProperties(flowConnection, { id: 'FOO_BAR' });
// then
expect(ids.assigned('FOO_BAR')).to.eql(flowBo);
expect(ids.assigned('SequenceFlow_1')).to.be.false;
expect(flowBo.id).to.equal('FOO_BAR');
expect(flowConnection.id).to.equal('FOO_BAR');
}));
it('updating extension elements', inject(
function(elementRegistry, modeling) {
// given
var flowConnection = elementRegistry.get('SequenceFlow_1'),
flowBo = flowConnection.businessObject;
// when
modeling.updateProperties(flowConnection, {
'xmlns:foo': 'http://foo',
'foo:customAttr': 'FOO'
});
// then
expect(flowBo.get('xmlns:foo')).to.equal('http://foo');
expect(flowBo.get('foo:customAttr')).to.equal('FOO');
}
));
it('setting di properties', inject(function(elementRegistry, modeling) {
// given
var flowConnection = elementRegistry.get('SequenceFlow_1'),
flowBo = flowConnection.businessObject,
flowDi = getDi(flowConnection);
// when
modeling.updateProperties(flowConnection, {
di: {
fill: 'FUCHSIA'
}
});
// then
expect(flowDi.fill).to.equal('FUCHSIA');
expect(flowBo.get('di')).not.to.exist;
}));
it('unsetting di properties', inject(function(elementRegistry, modeling) {
// given
var flowConnection = elementRegistry.get('SequenceFlow_1'),
flowDi = getDi(flowConnection);
modeling.updateProperties(flowConnection, { di: { fill: 'FUCHSIA' } });
// when
modeling.updateProperties(flowConnection, { di: { fill: undefined } });
// then
expect(flowDi.fill).not.to.exist;
}));
});
describe('should undo', function() {
it('setting loop characteristics', inject(
function(elementRegistry, modeling, commandStack, moddle) {
// given
var loopCharactersistics = moddle.create('bpmn:MultiInstanceLoopCharacteristics');
var taskShape = elementRegistry.get('ServiceTask_1');
// when
modeling.updateProperties(taskShape, { loopCharacteristics: loopCharactersistics });
commandStack.undo();
// then
expect(taskShape.businessObject.loopCharactersistics).not.to.exist;
}
));
it('unsetting default flow', inject(
function(elementRegistry, commandStack, modeling) {
// given
var gatewayShape = elementRegistry.get('ExclusiveGateway_1'),
gatewayBo = gatewayShape.businessObject,
oldDefaultBo = gatewayShape.businessObject['default'],
oldDefaultConnection = elementRegistry.get(oldDefaultBo.id);
// when
modeling.updateProperties(gatewayShape, {
'default': undefined
});
commandStack.undo();
// then
expect(gatewayBo['default']).to.eql(oldDefaultBo);
// flow got updated, too
expect(updatedElements).to.include(oldDefaultConnection);
}
));
it('updating default flow', inject(
function(elementRegistry, commandStack, modeling) {
// given
var gatewayShape = elementRegistry.get('ExclusiveGateway_1'),
gatewayBo = gatewayShape.businessObject,
newDefaultFlowConnection = elementRegistry.get('SequenceFlow_2'),
newDefaultFlow = newDefaultFlowConnection.businessObject,
oldDefaultFlowConnection = elementRegistry.get('SequenceFlow_1'),
oldDefaultFlow = oldDefaultFlowConnection.businessObject;
// when
modeling.updateProperties(gatewayShape, {
'default': newDefaultFlow
});
commandStack.undo();
// then
expect(gatewayBo['default']).to.eql(oldDefaultFlow);
// flow got updated, too
expect(updatedElements).to.include(newDefaultFlowConnection);
expect(updatedElements).to.include(oldDefaultFlowConnection);
}
));
it('updating name', inject(
function(elementRegistry, commandStack, modeling) {
// given
var flowConnection = elementRegistry.get('SequenceFlow_1');
// when
modeling.updateProperties(flowConnection, { name: 'FOO BAR' });
commandStack.undo();
// then
expect(flowConnection.businessObject.name).to.equal('default');
// flow got updated, too
expect(updatedElements).to.include(flowConnection.label);
}
));
it('unsetting name', inject(
function(elementRegistry, commandStack, modeling) {
// given
var flowConnection = elementRegistry.get('SequenceFlow_3');
modeling.updateProperties(flowConnection, { name: undefined });
// when
commandStack.undo();
// then
expect(flowConnection.businessObject.name).to.equal('conditional');
}
));
it('updating id', inject(function(elementRegistry, commandStack, modeling) {
// given
var flowConnection = elementRegistry.get('SequenceFlow_1'),
flowBo = flowConnection.businessObject;
var ids = flowBo.$model.ids;
// when
modeling.updateProperties(flowConnection, { id: 'FOO_BAR' });
commandStack.undo();
// then
expect(ids.assigned('FOO_BAR')).to.be.false;
expect(ids.assigned('SequenceFlow_1')).to.eql(flowBo);
expect(flowConnection.id).to.equal('SequenceFlow_1');
expect(flowBo.id).to.equal('SequenceFlow_1');
}));
it('updating extension elements', inject(
function(elementRegistry, commandStack, modeling) {
// given
var flowConnection = elementRegistry.get('SequenceFlow_1'),
flowBo = flowConnection.businessObject;
modeling.updateProperties(flowConnection, {
'xmlns:foo': 'http://foo',
'foo:customAttr': 'FOO'
});
// when
commandStack.undo();
// then
expect(flowBo.get('xmlns:foo')).not.to.exist;
expect(flowBo.get('foo:customAttr')).not.to.exist;
}
));
});
describe('should redo', function() {
it('setting loop characteristics', inject(
function(elementRegistry, modeling, commandStack, moddle) {
// given
var loopCharacteristics = moddle.create(
'bpmn:MultiInstanceLoopCharacteristics'
);
var taskShape = elementRegistry.get('ServiceTask_1'),
taskBo = taskShape.businessObject;
// when
modeling.updateProperties(taskShape, {
loopCharacteristics: loopCharacteristics
});
commandStack.undo();
commandStack.redo();
// then
expect(taskBo.loopCharacteristics).to.eql(loopCharacteristics);
}
));
it('updating default flow', inject(
function(elementRegistry, commandStack, modeling) {
// given
var gatewayShape = elementRegistry.get('ExclusiveGateway_1');
// when
modeling.updateProperties(gatewayShape, { 'default': undefined });
commandStack.undo();
commandStack.redo();
// then
expect(gatewayShape.businessObject['default']).not.to.exist;
// flow got updated, too
expect(updatedElements).to.include(
elementRegistry.get('SequenceFlow_1')
);
}
));
it('updating name', inject(
function(elementRegistry, commandStack, modeling) {
// given
var flowConnection = elementRegistry.get('SequenceFlow_1');
// when
modeling.updateProperties(flowConnection, { name: 'FOO BAR' });
commandStack.undo();
commandStack.redo();
// then
expect(flowConnection.businessObject.name).to.equal('FOO BAR');
// flow got updated, too
expect(updatedElements).to.include(flowConnection.label);
}
));
it('unsetting name', inject(
function(elementRegistry, commandStack, modeling) {
// given
var flowConnection = elementRegistry.get('SequenceFlow_3');
modeling.updateProperties(flowConnection, { name: undefined });
// when
commandStack.undo();
commandStack.redo();
// then
expect(flowConnection.businessObject.name).not.to.exist;
}
));
});
describe('unwrap diagram elements', function() {
it('updating default flow with connection', inject(
function(elementRegistry, modeling) {
// given
var gatewayShape = elementRegistry.get('ExclusiveGateway_1'),
newDefaultFlowConnection = elementRegistry.get('SequenceFlow_2');
// when
modeling.updateProperties(gatewayShape, {
'default': newDefaultFlowConnection
});
// then
expect(gatewayShape.businessObject['default']).to.eql(
newDefaultFlowConnection.businessObject
);
// flow got updated, too
expect(updatedElements).to.include(newDefaultFlowConnection);
}
));
});
describe('error handling', function() {
it('should ignore unchanged id', inject(
function(elementRegistry, modeling) {
// given
var flowConnection = elementRegistry.get('SequenceFlow_1'),
flowBo = flowConnection.businessObject;
var ids = flowBo.$model.ids;
// when
modeling.updateProperties(flowConnection, { id: 'SequenceFlow_1' });
// then
expect(ids.assigned('SequenceFlow_1')).to.eql(flowBo);
expect(flowBo.id).to.equal('SequenceFlow_1');
}
));
it('should ignore setting color on elements without di', inject(
function(modeling, bpmnFactory) {
// given
var rootElement = bpmnFactory.create('bpmn:RootElement');
// when
modeling.updateProperties(rootElement, {
di: {
fill: 'fuchsia'
}
});
// then
expect(getDi(rootElement)).not.to.exist;
}
));
});
});
================================================
FILE: test/spec/features/modeling/UpdateSemanticParent.bpmn
================================================
================================================
FILE: test/spec/features/modeling/UpdateSemanticParentSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import { getDi } from 'lib/util/ModelUtil';
import modelingModule from 'lib/features/modeling';
import coreModule from 'lib/core';
describe('features/modeling/behavior - update semantic parent', function() {
var diagramXML = require('./UpdateSemanticParent.bpmn');
var participant1Bo, participant1Di, participant2Bo, participant2Di, dataStoreBo, dataStoreDi;
beforeEach(bootstrapModeler(diagramXML, {
modules: [
coreModule,
modelingModule
]
}));
beforeEach(inject(function(commandStack, elementRegistry) {
var participant1 = elementRegistry.get('Participant_1'),
participant2 = elementRegistry.get('Participant_2'),
dataStore = elementRegistry.get('DataStoreReference');
participant1Bo = participant1.businessObject;
participant1Di = getDi(participant1);
participant2Bo = participant2.businessObject;
participant2Di = getDi(participant2);
dataStoreBo = dataStore.businessObject;
dataStoreDi = getDi(dataStore);
// when
commandStack.execute('dataStore.updateContainment', {
dataStoreBo: dataStoreBo,
dataStoreDi: dataStoreDi,
newSemanticParent: participant2Bo.processRef,
newDiParent: participant2Di
});
}));
it('should do', function() {
// then
expect(dataStoreBo.$parent).to.eql(participant2Bo.processRef);
expect(dataStoreDi.$parent).to.eql(participant2Di.$parent);
});
it('should undo', inject(function(commandStack) {
// when
commandStack.undo();
// then
expect(dataStoreBo.$parent).to.eql(participant1Bo.processRef);
expect(dataStoreDi.$parent).to.eql(participant1Di.$parent);
}));
it('should redo', inject(function(commandStack) {
// when
commandStack.undo();
commandStack.redo();
// then
expect(dataStoreBo.$parent).to.eql(participant2Bo.processRef);
expect(dataStoreDi.$parent).to.eql(participant2Di.$parent);
}));
});
================================================
FILE: test/spec/features/modeling/append/TextAnnotationSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import {
find
} from 'min-dash';
import modelingModule from 'lib/features/modeling';
import coreModule from 'lib/core';
describe('features/modeling - append text-annotation', function() {
var diagramXML = require('../../../../fixtures/bpmn/containers.bpmn');
var testModules = [ coreModule, modelingModule ];
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
describe('should append', function() {
it('in lane');
it('in participant', inject(function(elementRegistry, modeling, canvas) {
// given
var eventShape = elementRegistry.get('IntermediateCatchEvent_1'),
collaboration = elementRegistry.get('_Collaboration_2').businessObject;
// when
var annotationShape = modeling.appendShape(eventShape, { type: 'bpmn:TextAnnotation' }),
annotation = annotationShape.businessObject;
var connectingConnection = find(annotationShape.incoming, function(c) {
return c.target === annotationShape;
});
var connecting = connectingConnection.businessObject;
// then
expect(annotationShape).to.exist;
expect(annotation.$instanceOf('bpmn:TextAnnotation')).to.be.true;
expect(connecting.$instanceOf('bpmn:Association')).to.be.true;
expect(connecting.sourceRef).to.eql(eventShape.businessObject);
expect(connecting.targetRef).to.eql(annotation);
// correctly assign artifact parent
expect(annotation.$parent).to.eql(collaboration);
expect(connecting.$parent).to.eql(collaboration);
expect(collaboration.artifacts).to.include(annotation);
expect(collaboration.artifacts).to.include(connecting);
}));
it('in sub process', inject(function(elementRegistry, modeling) {
// given
var eventShape = elementRegistry.get('IntermediateThrowEvent_1');
// when
var annotationShape = modeling.appendShape(eventShape, { type: 'bpmn:TextAnnotation' }),
annotation = annotationShape.businessObject;
var connectingConnection = find(annotationShape.incoming, function(c) {
return c.target === annotationShape;
});
var connecting = connectingConnection.businessObject;
// then
expect(annotationShape).to.exist;
expect(annotation.$instanceOf('bpmn:TextAnnotation')).to.be.true;
expect(connecting.$instanceOf('bpmn:Association')).to.be.true;
expect(connecting.sourceRef).to.eql(eventShape.businessObject);
expect(connecting.targetRef).to.eql(annotation);
// correctly assign artifact parent
expect(annotation.$parent.id).to.equal('_Collaboration_2');
expect(connecting.$parent.id).to.equal('_Collaboration_2');
}));
it('with right size', inject(function(elementRegistry, elementFactory, modeling) {
// given
var eventShape = elementRegistry.get('IntermediateCatchEvent_1');
// when
var annotationShape = modeling.appendShape(eventShape, { type: 'bpmn:TextAnnotation' });
// then
expect(annotationShape.width).to.eql(100);
expect(annotationShape.height).to.eql(30);
}));
});
describe('undo', function() {
it('should undo wire connection source + target', inject(function(elementRegistry, modeling, commandStack) {
// given
var eventShape = elementRegistry.get('IntermediateCatchEvent_1'),
collaboration = elementRegistry.get('_Collaboration_2').businessObject;
var annotationShape = modeling.appendShape(eventShape, { type: 'bpmn:TextAnnotation' }),
annotation = annotationShape.businessObject;
var connectingConnection = find(annotationShape.incoming, function(c) {
return c.target === annotationShape;
});
var connecting = connectingConnection.businessObject;
// when
commandStack.undo();
// then
expect(connecting.sourceRef).to.be.null;
expect(connecting.targetRef).to.be.null;
expect(connecting.$parent).to.be.null;
expect(collaboration.artifacts).not.to.include(connecting);
expect(annotation.$parent).to.be.null;
expect(collaboration.artifacts).not.to.include(annotation);
}));
});
});
================================================
FILE: test/spec/features/modeling/behavior/AdaptiveLabelPositioningBehavior.basics.bpmn
================================================
SequenceFlow_1
SequenceFlow_2
SequenceFlow_2
SequenceFlow_1
SequenceFlow_1qmllcx
SequenceFlow_0s993e4
SequenceFlow_022at7e
SequenceFlow_1qmllcx
SequenceFlow_0s993e4
SequenceFlow_022at7e
SequenceFlow_0isa70k
SequenceFlow_0isa70k
SequenceFlow_3
SequenceFlow_3
Flow_167deqo
Flow_167deqo
foo
================================================
FILE: test/spec/features/modeling/behavior/AdaptiveLabelPositioningBehavior.boundary-events.bpmn
================================================
SequenceFlow_1
SequenceFlow_1
SequenceFlow_2
SequenceFlow_2
SequenceFlow_3
SequenceFlow_3
================================================
FILE: test/spec/features/modeling/behavior/AdaptiveLabelPositioningBehaviorSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import { getOrientation } from 'diagram-js/lib/layout/LayoutUtil';
import modelingModule from 'lib/features/modeling';
import coreModule from 'lib/core';
var testModules = [
modelingModule,
coreModule
];
var ATTACH = { attach: true };
describe('modeling/behavior - AdaptiveLabelPositioningBehavior', function() {
function expectLabelOrientation(element, expectedOrientation) {
var label = element.label;
// assume
expect(label).to.exist;
// when
var orientation = getOrientation(label, element);
// then
expect(orientation).to.eql(expectedOrientation);
}
describe('basics', function() {
var diagramXML = require('./AdaptiveLabelPositioningBehavior.basics.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
describe('on connect', function() {
it('should move label from LEFT to TOP', inject(function(elementRegistry, modeling) {
// given
var source = elementRegistry.get('LabelBottom'),
target = elementRegistry.get('LabelLeft');
// when
modeling.connect(source, target);
// then
expectLabelOrientation(source, 'bottom');
expectLabelOrientation(target, 'top');
}));
it('should move label from BOTTOM to TOP', inject(function(elementRegistry, modeling) {
// given
var source = elementRegistry.get('LabelBottom'),
target = elementRegistry.get('LabelRight');
// when
modeling.connect(source, target);
// then
expectLabelOrientation(source, 'top');
expectLabelOrientation(target, 'right');
}));
it('should move label from RIGHT to TOP', inject(function(elementRegistry, modeling) {
// given
var source = elementRegistry.get('LabelRight'),
target = elementRegistry.get('LabelTop');
// when
modeling.connect(source, target);
// then
expectLabelOrientation(source, 'top');
expectLabelOrientation(target, 'top');
}));
it('should move label from TOP to LEFT', inject(function(elementRegistry, modeling) {
// given
var source = elementRegistry.get('LabelTop'),
target = elementRegistry.get('LabelLeft');
// when
modeling.connect(source, target);
// then
expectLabelOrientation(source, 'left');
expectLabelOrientation(target, 'left');
}));
it('should move label from TOP to LEFT (inverse)', inject(function(elementRegistry, modeling) {
// given
var source = elementRegistry.get('LabelLeft'),
target = elementRegistry.get('LabelTop');
// when
modeling.connect(source, target);
// then
expectLabelOrientation(target, 'left');
expectLabelOrientation(source, 'left');
}));
it('should keep unaligned labels AS IS', inject(function(elementRegistry, modeling) {
// given
var source = elementRegistry.get('LabelBottomLeft'),
target = elementRegistry.get('LabelTop');
// when
modeling.connect(source, target);
// then
expectLabelOrientation(source, 'bottom');
expectLabelOrientation(target, 'top');
}));
it('should keep label where it is, if no options', inject(function(elementRegistry, modeling) {
// given
var source = elementRegistry.get('LabelImpossible'),
target = elementRegistry.get('Task');
// when
modeling.connect(source, target);
// then
expectLabelOrientation(source, 'right');
}));
});
describe('on reconnect', function() {
it('should move label from TOP to BOTTOM', inject(function(elementRegistry, modeling) {
// given
var connection = elementRegistry.get('SequenceFlow_1'),
source = elementRegistry.get('LabelTop'),
target = elementRegistry.get('LabelLeft');
// when
modeling.reconnectEnd(connection, target, { x: target.x + target.width / 2, y: target.y });
// then
expectLabelOrientation(source, 'bottom');
expectLabelOrientation(target, 'left');
}));
});
describe('on target move / layout', function() {
it('should move label from TOP to BOTTOM', inject(function(elementRegistry, modeling) {
// given
var source = elementRegistry.get('LabelTop'),
target = elementRegistry.get('LabelBottom_3');
// when
modeling.moveElements([ source ], { x: 0, y: 300 });
// then
expectLabelOrientation(source, 'bottom');
expectLabelOrientation(target, 'top');
}));
});
describe('on source move / layout', function() {
it('should move label from BOTTOM to TOP', inject(
function(elementRegistry, modeling) {
// given
var source = elementRegistry.get('LabelTop'),
target = elementRegistry.get('LabelBottom_3');
// when
modeling.moveElements([ target ], { x: 20, y: -300 });
// then
expectLabelOrientation(source, 'bottom');
expectLabelOrientation(target, 'top');
}
));
});
describe('on annotation move / layout', function() {
it('should not move label', inject(function(elementRegistry, modeling) {
// given
var source = elementRegistry.get('Flow_167deqo'),
target = elementRegistry.get('TextAnnotation_1vnawwd');
const prevCoordinates = { x: source.x, y: source.y };
modeling.moveElements([ target ], { x: -110, y: 20 });
const newCoordinates = { x: source.x, y: source.y };
// then
expect(prevCoordinates).to.eql(newCoordinates);
}));
});
describe('on waypoints update', function() {
it('should move label from RIGHT to TOP', inject(function(elementRegistry, modeling) {
// given
var connection = elementRegistry.get('SequenceFlow_2'),
source = elementRegistry.get('LabelRight'),
target = elementRegistry.get('LabelBottom');
// when
modeling.updateWaypoints(connection, [
{
original: { x: 131, y: 248 },
x: 131,
y: 248
},
{
x: 250,
y: 248
},
{
x: 250,
y: 394
},
{
original: { x: 131, y: 394 },
x: 131,
y: 394
},
]);
// then
expectLabelOrientation(source, 'top');
expectLabelOrientation(target, 'bottom');
}));
});
describe('on label creation', function() {
describe('through ', function() {
it('should create label at TOP', inject(
function(elementRegistry, modeling) {
// given
var element = elementRegistry.get('NoLabel');
// when
modeling.updateProperties(element, { name: 'FOO BAR' });
// then
expectLabelOrientation(element, 'top');
}
));
});
});
});
describe('boundary-events', function() {
var diagramXML = require('./AdaptiveLabelPositioningBehavior.boundary-events.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
describe('on boundary label creation', function() {
it('should NOT create label onto host', inject(
function(elementRegistry, modeling) {
// given
var element = elementRegistry.get('BoundaryEvent_1');
// when
modeling.updateProperties(element, { name: 'FOO BAR' });
// then
expectLabelOrientation(element, 'bottom');
}
));
it('should create label to the left', inject(
function(elementRegistry, modeling) {
// given
var element = elementRegistry.get('BoundaryEvent_4');
// when
modeling.updateProperties(element, { name: 'FOO BAR' });
// then
expectLabelOrientation(element, 'left');
}
));
});
describe('on connect', function() {
it('should keep label where it is if no better position', inject(
function(elementRegistry, modeling) {
// given
var source = elementRegistry.get('BoundaryEvent_2'),
target = elementRegistry.get('EndEvent_2');
// when
modeling.connect(source, target);
// then
expectLabelOrientation(source, 'right');
}
));
});
describe('on reconnect', function() {
it('should keep label where it is if no better position', inject(
function(elementRegistry, modeling) {
// given
var source = elementRegistry.get('BoundaryEvent_3'),
target = elementRegistry.get('EndEvent_1'),
connection = elementRegistry.get('SequenceFlow_2');
// when
modeling.reconnectEnd(connection, target, { x: target.x + target.width / 2, y: target.y });
// then
expectLabelOrientation(source, 'bottom');
}
));
});
describe('on boundary move', function() {
it('should move label to the left', inject(
function(elementRegistry, modeling) {
// given
var element = elementRegistry.get('BoundaryEvent_3'),
host = elementRegistry.get('Task_3');
// when
modeling.moveElements([ element ], { x: -50, y: -50 }, host, ATTACH);
// then
expectLabelOrientation(element, 'left');
}
));
it('should move label to the top', inject(
function(elementRegistry, modeling) {
// given
var element = elementRegistry.get('BoundaryEvent_3'),
host = elementRegistry.get('Task_3');
// when
modeling.moveElements([ element ], { x: 50, y: -80 }, host, ATTACH); // top-right corner
// then
expectLabelOrientation(element, 'top');
}
));
});
});
describe('integration', function() {
describe('copy and paste', function() {
var diagramXML = require('./AdaptiveLabelPositioningBehavior.basics.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
it('should NOT adjust on paste', inject(
function(canvas, copyPaste, elementRegistry, modeling) {
// given
var exclusiveGateway = elementRegistry.get('ExclusiveGateway_1'),
endEvent = elementRegistry.get('EndEvent_1');
var moveShapeSpy = sinon.spy(modeling, 'moveShape');
// when
copyPaste.copy([ exclusiveGateway, endEvent ]);
copyPaste.paste({
element: canvas.getRootElement(),
point: {
x: 1000,
y: 1000
}
});
// then
expect(moveShapeSpy).not.to.have.been.called;
})
);
});
});
});
================================================
FILE: test/spec/features/modeling/behavior/AssociationBehavior.bpmn
================================================
================================================
FILE: test/spec/features/modeling/behavior/AssociationBehaviorSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import modelingModule from 'lib/features/modeling';
describe('modeling/behavior - AssociationBehavior', function() {
var diagramXML = require('./AssociationBehavior.bpmn');
beforeEach(bootstrapModeler(diagramXML, { modules: modelingModule }));
it('should move to new parent on source move', inject(function(modeling, elementRegistry) {
// given
var association = elementRegistry.get('Association_1'),
process = elementRegistry.get('Process_1'),
startEvent = elementRegistry.get('StartEvent_1');
// when
modeling.moveElements([ startEvent ], { x: 100, y: 100 }, process);
// then
expect(association.parent).to.equal(process);
}));
it('should move to new parent on target move', inject(function(modeling, elementRegistry) {
// given
var association = elementRegistry.get('Association_1'),
process = elementRegistry.get('Process_1'),
textAnnotation = elementRegistry.get('TextAnnotation_1');
// when
modeling.moveElements([ textAnnotation ], { x: 100, y: 100 }, process);
// then
expect(association.parent).to.equal(process);
}));
});
================================================
FILE: test/spec/features/modeling/behavior/AttachEventBehavior.bpmn
================================================
bar
SequenceFlow_1
SequenceFlow_2
SequenceFlow_2
SequenceFlow_1
================================================
FILE: test/spec/features/modeling/behavior/AttachEventBehaviorSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import coreModule from 'lib/core';
import modelingModule from 'lib/features/modeling';
import { getBusinessObject } from '../../../../../lib/util/ModelUtil';
describe('features/modeling/behavior - attach events', function() {
var testModules = [
coreModule,
modelingModule
];
var attachEventBehaviorXML = require('./AttachEventBehavior.bpmn');
beforeEach(bootstrapModeler(attachEventBehaviorXML, { modules: testModules }));
describe('basics', function() {
describe('create', function() {
it('should replace', inject(function(elementFactory, elementRegistry, modeling) {
// given
var task = elementRegistry.get('Task_1'),
taskBo = getBusinessObject(task),
intermediateEvent = elementFactory.createShape({ type: 'bpmn:IntermediateThrowEvent' });
// when
var boundaryEvent = modeling.createElements(
[ intermediateEvent ], { x: 300, y: 140 }, task, { attach: true }
)[0];
// then
var boundaryEventBo = getBusinessObject(boundaryEvent);
expect(boundaryEventBo.$type).to.equal('bpmn:BoundaryEvent');
expect(boundaryEventBo.attachedToRef).to.equal(taskBo);
}));
it('should NOT replace', inject(function(elementFactory, elementRegistry, modeling) {
// given
var process = elementRegistry.get('Process_1'),
intermediateEvent = elementFactory.createShape({ type: 'bpmn:IntermediateThrowEvent' });
// when
intermediateEvent = modeling.createElements([ intermediateEvent ], { x: 300, y: 240 }, process)[0];
// then
var intermediateEventBo = getBusinessObject(intermediateEvent);
expect(intermediateEventBo.$type).to.equal('bpmn:IntermediateThrowEvent');
expect(intermediateEventBo.attachedToRef).not.to.exist;
}));
it('should copy properties', inject(
function(bpmnFactory, elementFactory, elementRegistry, modeling) {
// given
var task = elementRegistry.get('Task_1');
var intermediateThrowEventBo = bpmnFactory.create('bpmn:IntermediateThrowEvent', {
name: 'foo'
});
var documentation = bpmnFactory.create('bpmn:Documentation', {
text: 'bar'
});
intermediateThrowEventBo.documentation = [ documentation ];
documentation.$parent = intermediateThrowEventBo;
var intermediateThrowEvent = elementFactory.createShape({
type: 'bpmn:IntermediateThrowEvent',
businessObject: intermediateThrowEventBo
});
// when
var boundaryEvent = modeling.createElements(
[ intermediateThrowEvent ], { x: 300, y: 140 }, task, { attach: true }
)[0];
// then
var boundaryEventBo = getBusinessObject(boundaryEvent);
expect(boundaryEventBo.name).to.equal('foo');
expect(boundaryEventBo.documentation).to.have.lengthOf(1);
expect(boundaryEventBo.documentation[0].text).to.equal('bar');
}
));
});
describe('move', function() {
it('should replace', inject(function(elementRegistry, modeling) {
// given
var task = elementRegistry.get('Task_1'),
taskBo = getBusinessObject(task),
intermediateThrowEvent = elementRegistry.get('IntermediateThrowEvent_1');
// when
modeling.moveElements([ intermediateThrowEvent ], { x: 100, y: 40 }, task, { attach: true });
// then
var boundaryEvent = elementRegistry.get('IntermediateThrowEvent_1'),
boundaryEventBo = getBusinessObject(boundaryEvent);
expect(boundaryEvent).to.exist;
expect(boundaryEventBo.$type).to.equal('bpmn:BoundaryEvent');
expect(boundaryEventBo.attachedToRef).to.equal(taskBo);
}));
it('should NOT replace', inject(function(elementRegistry, modeling) {
// given
var process = elementRegistry.get('Process_1'),
intermediateThrowEvent = elementRegistry.get('IntermediateThrowEvent_1'),
intermediateThrowEventBo = getBusinessObject(intermediateThrowEvent);
// when
modeling.moveElements([ intermediateThrowEvent ], { x: 100, y: 100 }, process);
// then
expect(intermediateThrowEvent).to.exist;
expect(intermediateThrowEventBo.$type).to.equal('bpmn:IntermediateThrowEvent');
expect(intermediateThrowEventBo.attachedToRef).not.to.exist;
}));
describe('properties', function() {
it('should copy properties', inject(function(elementRegistry, modeling) {
// given
var task = elementRegistry.get('Task_1'),
intermediateThrowEvent = elementRegistry.get('IntermediateThrowEvent_1');
// when
modeling.moveElements([ intermediateThrowEvent ], { x: 100, y: 40 }, task, { attach: true });
// then
var boundaryEvent = elementRegistry.get('IntermediateThrowEvent_1'),
boundaryEventBo = getBusinessObject(boundaryEvent);
expect(boundaryEventBo.name).to.equal('foo');
expect(boundaryEventBo.documentation).to.have.lengthOf(1);
expect(boundaryEventBo.documentation[0].text).to.equal('bar');
}));
describe('event definitions', function() {
var ids = [
'ConditionalCatchEvent',
'IntermediateThrowEvent_1',
'MessageCatchEvent',
'SignalCatchEvent',
'TimerCatchEvent',
];
function getDelta(element, task) {
return {
x: task.x + task.width / 2 - element.x - element.width / 2,
y: task.y + task.height - element.y - element.height / 2
};
}
ids.forEach(function(id) {
it('should copy event definition ' + id, inject(function(elementRegistry, modeling) {
// given
var element = elementRegistry.get(id),
elementBo = getBusinessObject(element),
eventDefinitions = elementBo.get('eventDefinitions'),
task = elementRegistry.get('Task_1');
// when
modeling.moveElements([ element ], getDelta(element, task), task, { attach: true });
// then
var boundaryEvent = elementRegistry.get(id),
boundaryEventBo = getBusinessObject(boundaryEvent);
expect(boundaryEventBo.$type).to.equal('bpmn:BoundaryEvent');
expect(boundaryEventBo.eventDefinitions).to.jsonEqual(eventDefinitions, skipId);
}));
});
});
});
});
});
describe('connections', function() {
it('should remove incoming connection', inject(function(elementRegistry, modeling) {
// given
var intermediateThrowEvent = elementRegistry.get('IntermediateThrowEvent_1'),
startEvent = elementRegistry.get('StartEvent_1'),
task = elementRegistry.get('Task_1');
// when
modeling.moveElements([ intermediateThrowEvent ], { x: 100, y: 40 }, task, { attach: true });
// then
var boundaryEvent = elementRegistry.get('IntermediateThrowEvent_1');
expect(boundaryEvent.incoming).to.have.lengthOf(0);
expect(startEvent.outgoing).to.have.lengthOf(0);
}));
it('should NOT remove outgoing connection', inject(function(elementRegistry, modeling) {
// given
var intermediateThrowEvent = elementRegistry.get('IntermediateThrowEvent_1'),
task = elementRegistry.get('Task_1');
// when
modeling.moveElements([ intermediateThrowEvent ], { x: 100, y: 40 }, task, { attach: true });
// then
var boundaryEvent = elementRegistry.get('IntermediateThrowEvent_1');
expect(boundaryEvent.outgoing).to.have.lengthOf(1);
expect(task.incoming).to.have.lengthOf(1);
}));
it('should lay out connection once', inject(function(elementRegistry, eventBus, modeling) {
// given
var intermediateThrowEvent = elementRegistry.get('IntermediateThrowEvent_1'),
task = elementRegistry.get('Task_1');
var layoutSpy = sinon.spy();
eventBus.on('commandStack.connection.layout.execute', layoutSpy);
// when
modeling.moveElements([ intermediateThrowEvent ], { x: 100, y: 40 }, task, { attach: true });
// then
expect(layoutSpy).to.be.calledOnce;
}));
});
});
// helpers //////////
function skipId(key, value) {
if (key === 'id') {
return;
}
return value;
}
================================================
FILE: test/spec/features/modeling/behavior/BoundaryEvent.bpmn
================================================
================================================
FILE: test/spec/features/modeling/behavior/BoundaryEventBehaviorSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import modelingModule from 'lib/features/modeling';
import coreModule from 'lib/core';
import { is } from 'lib/util/ModelUtil';
describe('features/modeling/behavior - boundary event', function() {
var testModules = [ coreModule, modelingModule ];
var diagramXML = require('./BoundaryEvent.bpmn');
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
describe('should implicitly remove boundary events', function() {
it('after connecting to event-based gateway',
inject(function(modeling, elementRegistry) {
// given
var eventBasedGateway = elementRegistry.get('EventBasedGateway_1'),
receiveTask = elementRegistry.get('ReceiveTask_1'),
boundaryEvent = elementRegistry.get('BoundaryEvent_1');
// when
modeling.connect(eventBasedGateway, receiveTask, {
type: 'bpmn:SequenceFlow'
});
// then
expect(elementRegistry.get(boundaryEvent.id)).not.to.exist;
})
);
it('after replacing connected gateway with event-based gateway',
inject(function(modeling, elementRegistry, bpmnReplace) {
// given
var gateway = elementRegistry.get('ExclusiveGateway_1'),
receiveTask = elementRegistry.get('ReceiveTask_1'),
boundaryEvent = elementRegistry.get('BoundaryEvent_1');
modeling.connect(gateway, receiveTask, {
type: 'bpmn:SequenceFlow'
});
// when
bpmnReplace.replaceElement(gateway, {
type: 'bpmn:EventBasedGateway'
});
// then
expect(elementRegistry.get(boundaryEvent.id)).not.to.exist;
})
);
});
describe('should keep root element reference on replace', function() {
describe('interrupting to non-interrupting', function() {
it('message reference', inject(function(bpmnReplace, elementRegistry) {
// given
var interruptingBoundaryEvent = elementRegistry.get('BoundaryEvent_2'),
message = getReferencedRootElement(interruptingBoundaryEvent, 'messageRef');
// assume
expect(is(message, 'bpmn:Message')).to.be.true;
// when
var nonInterruptingBoundaryEvent = bpmnReplace.replaceElement(interruptingBoundaryEvent, {
type: 'bpmn:BoundaryEvent',
eventDefinitionType: 'bpmn:MessageEventDefinition',
cancelActivity: false
});
// then
expect(getReferencedRootElement(nonInterruptingBoundaryEvent, 'messageRef')).to.equal(message);
}));
it('escalation reference', inject(function(bpmnReplace, elementRegistry) {
// given
var interruptingBoundaryEvent = elementRegistry.get('BoundaryEvent_3'),
escalation = getReferencedRootElement(interruptingBoundaryEvent, 'escalationRef');
// assume
expect(is(escalation, 'bpmn:Escalation')).to.be.true;
// when
var nonInterruptingBoundaryEvent = bpmnReplace.replaceElement(interruptingBoundaryEvent, {
type: 'bpmn:BoundaryEvent',
eventDefinitionType: 'bpmn:EscalationEventDefinition',
cancelActivity: false
});
// then
expect(getReferencedRootElement(nonInterruptingBoundaryEvent, 'escalationRef')).to.equal(escalation);
}));
it('error reference', inject(function(bpmnReplace, elementRegistry) {
// given
var interruptingBoundaryEvent = elementRegistry.get('BoundaryEvent_4'),
error = getReferencedRootElement(interruptingBoundaryEvent, 'errorRef');
// assume
expect(is(error, 'bpmn:Error')).to.be.true;
// when
var nonInterruptingBoundaryEvent = bpmnReplace.replaceElement(interruptingBoundaryEvent, {
type: 'bpmn:BoundaryEvent',
eventDefinitionType: 'bpmn:ErrorEventDefinition',
cancelActivity: false
});
// then
expect(getReferencedRootElement(nonInterruptingBoundaryEvent, 'errorRef')).to.equal(error);
}));
it('signal reference', inject(function(bpmnReplace, elementRegistry) {
// given
var interruptingBoundaryEvent = elementRegistry.get('BoundaryEvent_5'),
signal = getReferencedRootElement(interruptingBoundaryEvent, 'signalRef');
// assume
expect(is(signal, 'bpmn:Signal')).to.be.true;
// when
var nonInterruptingBoundaryEvent = bpmnReplace.replaceElement(interruptingBoundaryEvent, {
type: 'bpmn:BoundaryEvent',
eventDefinitionType: 'bpmn:SignalEventDefinition',
cancelActivity: false
});
// then
expect(getReferencedRootElement(nonInterruptingBoundaryEvent, 'signalRef')).to.equal(signal);
}));
});
describe('non-interrupting to interrupting', function() {
it('message reference', inject(function(bpmnReplace, elementRegistry) {
// given
var interruptingBoundaryEvent = elementRegistry.get('BoundaryEvent_6'),
message = getReferencedRootElement(interruptingBoundaryEvent, 'messageRef');
// assume
expect(is(message, 'bpmn:Message')).to.be.true;
// when
var nonInterruptingBoundaryEvent = bpmnReplace.replaceElement(interruptingBoundaryEvent, {
type: 'bpmn:BoundaryEvent',
eventDefinitionType: 'bpmn:MessageEventDefinition',
cancelActivity: true
});
// then
expect(getReferencedRootElement(nonInterruptingBoundaryEvent, 'messageRef')).to.equal(message);
}));
});
});
});
// helpers //////////
function getReferencedRootElement(element, propertyName) {
var businessObject = element.businessObject,
eventDefinition = businessObject.eventDefinitions[ 0 ];
return eventDefinition.get(propertyName);
}
================================================
FILE: test/spec/features/modeling/behavior/CompensateBoundaryEventBehavior.bpmn
================================================
NoneFlow
NoneFlow
Flow_1czca1o
Flow_0ma72c1
Flow_0ma72c1
Flow_1czca1o
Flow_1t3tgme
Flow_19nk7s2
Flow_1t3tgme
Flow_19nk7s2
Flow_0d1dx68
Flow_158hpoy
Flow_0d1dx68
Flow_158hpoy
Flow_1s3u9a0
Flow_1s3u9a0
================================================
FILE: test/spec/features/modeling/behavior/CompensateBoundaryEventBehaviorSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import { is } from 'lib/util/ModelUtil';
import copyPasteModule from 'lib/features/copy-paste';
import coreModule from 'lib/core';
import modelingModule from 'lib/features/modeling';
import diagramXML from './CompensateBoundaryEventBehavior.bpmn';
describe('features/modeling/behavior - compensation boundary event', function() {
const testModules = [
copyPasteModule,
coreModule,
modelingModule
];
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
describe('should add `isForCompensation`', function() {
it('on append', inject(function(elementFactory, modeling, elementRegistry) {
// given
const boundaryEventShape = elementRegistry.get('Attached_Event');
const taskShape = elementFactory.createShape({ type: 'bpmn:Task' });
// when
const task = modeling.appendShape(boundaryEventShape, taskShape, { x: 100, y: 100 });
// then
expect(task.businessObject.isForCompensation).to.be.true;
}));
it('on connect', inject(function(modeling, elementRegistry) {
// given
const boundaryEventShape = elementRegistry.get('Attached_Event');
const taskShape = elementRegistry.get('AnotherTask');
// when
modeling.connect(boundaryEventShape, taskShape);
// then
expect(taskShape.businessObject.isForCompensation).to.be.true;
}));
it('on reconnect start', inject(function(modeling, elementRegistry) {
// given
const compensateBoundaryEvent = elementRegistry.get('Attached_Event');
const sequenceFlow = elementRegistry.get('NoneFlow');
const task = sequenceFlow.target;
// when
modeling.reconnectStart(sequenceFlow, compensateBoundaryEvent, {
x: compensateBoundaryEvent.x,
y: compensateBoundaryEvent.y
});
// then
expect(task.businessObject.isForCompensation).to.be.true;
expect(task.incoming).to.have.length(1);
const incomingConnection = task.incoming[0];
expect(is(incomingConnection, 'bpmn:Association')).to.be.true;
expect(incomingConnection.businessObject).to.be.have.property('associationDirection', 'One');
}));
it('on reconnect end', inject(function(modeling, elementRegistry) {
// given
const taskShape = elementRegistry.get('AnotherTask');
const connection = elementRegistry.get('Association');
// when
modeling.reconnectEnd(connection, taskShape, { x: 100, y: 100 });
// then
expect(taskShape.businessObject.isForCompensation).to.be.true;
}));
it('on replace', inject(function(bpmnReplace, elementRegistry) {
// given
const event = elementRegistry.get('NoneEvent');
const task = elementRegistry.get('NoneActivity');
// when
bpmnReplace.replaceElement(event, {
type: 'bpmn:BoundaryEvent' ,
eventDefinitionType: 'bpmn:CompensateEventDefinition'
});
// then
expect(task.businessObject.isForCompensation).to.be.true;
expect(is(task.incoming[0], 'bpmn:Association')).to.be.true;
}));
});
describe('should remove `isForCompensation`', function() {
it('on remove element', inject(function(elementRegistry, modeling) {
// given
const taskShape = elementRegistry.get('Task_Compensation');
const boundaryEventShape = elementRegistry.get('Attached_Event2');
// then
expect(taskShape.businessObject.isForCompensation).to.be.true;
// when
modeling.removeElements([ boundaryEventShape ]);
// then
expect(taskShape.businessObject.isForCompensation).to.be.false;
}));
it('on remove connection', inject(function(elementRegistry, modeling) {
// given
const taskShape = elementRegistry.get('Task_Compensation');
const connection = elementRegistry.get('Association');
// then
expect(taskShape.businessObject.isForCompensation).to.be.true;
// when
modeling.removeConnection(connection);
// then
expect(taskShape.businessObject.isForCompensation).to.be.false;
}));
// TODO(@barmac): implement together with allowing the interaction in the rules
it.skip('on reconnect start', inject(function(modeling, elementRegistry) {
// given
const taskShape = elementRegistry.get('Task');
const compensationAssociation = elementRegistry.get('Association');
const compensationActivity = compensationAssociation.target;
// when
modeling.reconnectStart(compensationAssociation, taskShape, { x: taskShape.x, y: taskShape.y });
// then
expect(compensationActivity.businessObject.isForCompensation).to.be.false;
expect(compensationActivity.incoming).to.have.length(1);
const incomingConnection = compensationActivity.incoming[0];
expect(is(incomingConnection, 'bpmn:SequenceFlow')).to.be.true;
}));
it('on reconnect end', inject(function(modeling, elementRegistry) {
// given
const oldShape = elementRegistry.get('Task_Compensation');
const taskShape = elementRegistry.get('Task');
const connection = elementRegistry.get('Association');
// when
modeling.reconnectEnd(connection, taskShape, { x: taskShape.x, y: taskShape.y });
// then
expect(oldShape.businessObject.isForCompensation).to.be.false;
}));
it('on replace', inject(function(bpmnReplace, elementRegistry) {
// given
const event = elementRegistry.get('Attached_Event2');
const task = elementRegistry.get('Task_Compensation');
// when
bpmnReplace.replaceElement(event, { type: 'bpmn:BoundaryEvent', eventDefinitionType: 'bpmn:MessageEventDefinition' });
// then
expect(task.businessObject.isForCompensation).to.be.false;
expect(is(task.incoming[0], 'bpmn:SequenceFlow')).to.be.true;
}));
});
describe('remove connections', function() {
it('should remove disallowed connections on connect', inject(function(modeling, elementRegistry) {
// given
const task = elementRegistry.get('Task_DisallowedConnections');
const event = elementRegistry.get('Attached_Event');
expect(task.incoming).to.have.length(1);
expect(task.outgoing).to.have.length(1);
// when
modeling.connect(event, task);
// then
expect(task.incoming).to.have.length(1);
expect(task.outgoing).to.have.length(0);
}));
it('should remove disallowed connections on reconnect', inject(function(modeling, elementRegistry) {
// given
const task = elementRegistry.get('Task_DisallowedConnections');
const connection = elementRegistry.get('Association');
// when
modeling.reconnectEnd(connection, task, { x: 100, y: 100 });
// then
expect(task.incoming).to.have.length(1);
expect(task.outgoing).to.have.length(0);
}));
it('should remove existing compensation association when new one is created', inject(
function(modeling, elementRegistry) {
// given
const task = elementRegistry.get('AnotherTask');
const event = elementRegistry.get('Attached_Event2');
// when
modeling.connect(event, task);
// then
expect(task.incoming).to.have.length(1);
expect(event.outgoing).to.have.length(1);
expect(task.incoming[0]).to.eql(event.outgoing[0]);
}
));
it('should remove association when no longer for compensation', inject(
function(bpmnReplace, elementRegistry) {
// given
let event = elementRegistry.get('ReplacedEvent'),
compensationActivity = event.outgoing[0].target;
// when
event = bpmnReplace.replaceElement(event, {
type: 'bpmn:BoundaryEvent' ,
eventDefinitionType: 'bpmn:MessageEventDefinition'
});
// then
expect(compensationActivity.incoming).to.have.lengthOf(1);
expect(compensationActivity.incoming[0].source).to.eql(event);
}
));
});
describe('remove attachers', function() {
it('should remove attachers of compensation activity', inject(function(elementRegistry, modeling) {
// given
const event = elementRegistry.get('Attached_Event'),
task = elementRegistry.get('Task');
// when
modeling.connect(event, task);
// then
expect(task.attachers).to.be.empty;
expect(task.businessObject.isForCompensation).to.be.true;
}));
it('should NOT remove attachers of non-compensation activity', inject(function(elementRegistry, bpmnReplace) {
// given
let event = elementRegistry.get('MultiBoundary'),
tasks = event.outgoing.map(({ target }) => target);
// when
event = bpmnReplace.replaceElement(event, {
type: 'bpmn:BoundaryEvent' ,
eventDefinitionType: 'bpmn:CompensateEventDefinition'
});
// then
expect(event.outgoing).to.have.lengthOf(1);
const compensationAcivity = event.outgoing[0].target;
expect(compensationAcivity.businessObject.isForCompensation).to.be.true;
for (const task of tasks.filter(task => task !== compensationAcivity)) {
expect(task.attachers).to.have.lengthOf(1);
expect(task.businessObject.isForCompensation).to.be.false;
}
}));
});
it('should add `isForCompensation` to exactly 1 candidate activity on replace', inject(
function(bpmnReplace, elementRegistry) {
// given
let event = elementRegistry.get('MultiOutgoing');
const tasks = event.outgoing.map(flow => flow.target);
// when
event = bpmnReplace.replaceElement(event, {
type: 'bpmn:BoundaryEvent' ,
eventDefinitionType: 'bpmn:CompensateEventDefinition'
});
// then
expect(event.outgoing).to.have.lengthOf(1);
const target = event.outgoing[0].target;
expect(target.businessObject.isForCompensation).to.be.true;
const otherTasks = tasks.filter(task => task !== target);
for (const task of otherTasks) {
expect(task.businessObject.isForCompensation).to.be.false;
}
})
);
it('should NOT break when there are no outgoing connections (to compensation)', inject(
function(elementRegistry, bpmnReplace) {
// given
const event = elementRegistry.get('NoneBoundary');
// when
const action = () => {
bpmnReplace.replaceElement(event, {
type: 'bpmn:BoundaryEvent' ,
eventDefinitionType: 'bpmn:CompensateEventDefinition'
});
};
// then
expect(action).not.to.throw();
}
));
it('should NOT break when there are no outgoing connections (from compensation)', inject(
function(elementRegistry, bpmnReplace) {
// given
const event = elementRegistry.get('Attached_Event');
// when
const action = () => {
bpmnReplace.replaceElement(event, {
type: 'bpmn:BoundaryEvent' ,
eventDefinitionType: 'bpmn:MessageEventDefinition'
});
};
// then
expect(action).not.to.throw();
}
));
it('should NOT crash when core `replace` component is used', inject(
function(elementRegistry, replace) {
// given
const task = elementRegistry.get('Task_Compensation');
// when
const action = () => {
replace.replaceElement(task,
{
type: 'bpmn:ManualTask'
}
);
};
// then
expect(action).not.to.throw();
}
));
describe('copy and paste', function() {
it('should NOT break on copy and paste', inject(function(canvas, copyPaste, elementRegistry) {
// given
copyPaste.copy([
elementRegistry.get('Task_BoundaryEvent2'),
elementRegistry.get('Task_Compensation')
]);
// when
var copiedElements = copyPaste.paste({
element: canvas.getRootElement(),
point: {
x: 100,
y: 100
}
});
// then
expect(copiedElements).to.have.lengthOf(4);
expect(copiedElements.filter(element => is(element, 'bpmn:Association'))).to.have.length(1);
expect(copiedElements.filter(element => is(element, 'bpmn:BoundaryEvent'))).to.have.length(1);
expect(copiedElements.filter(element => is(element, 'bpmn:Task'))).to.have.length(2);
}));
});
});
================================================
FILE: test/spec/features/modeling/behavior/CompensationAssociationBehavior.bpmn
================================================
================================================
FILE: test/spec/features/modeling/behavior/CompensationAssociationBehaviorSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import {
getBusinessObject,
is
} from 'lib/util/ModelUtil';
import modelingModule from 'lib/features/modeling';
describe('modeling/behavior - CompensationAssociation', function() {
var diagramXML = require('./CompensationAssociationBehavior.bpmn');
beforeEach(bootstrapModeler(diagramXML, { modules: modelingModule }));
it('should use manhattan layout', inject(function(modeling, elementRegistry) {
// given
var boundaryShape = elementRegistry.get('CompensationBoundary'),
activityShape = elementRegistry.get('CompensationActivity');
// when
var newConnection = modeling.connect(boundaryShape, activityShape);
// then
expect(waypoints(newConnection)).to.eql([
{ x: 107, y: 142 },
{ x: 107, y: 254 },
{ x: 206, y: 254 }
]);
}));
it('should create directed association', inject(function(modeling, elementRegistry) {
// given
var boundaryShape = elementRegistry.get('CompensationBoundary'),
activityShape = elementRegistry.get('CompensationActivity');
// when
var newConnection = modeling.connect(boundaryShape, activityShape);
// then
expect(is(newConnection, 'bpmn:Association')).to.be.true;
expect(getBusinessObject(newConnection)).to.have.property('associationDirection', 'One');
}));
});
function waypoints(connection) {
return connection.waypoints.map(function(wp) {
return { x: wp.x, y: wp.y };
});
}
================================================
FILE: test/spec/features/modeling/behavior/CreateBehavior.bpmn
================================================
================================================
FILE: test/spec/features/modeling/behavior/CreateBehaviorSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import coreModule from 'lib/core';
import modelingModule from 'lib/features/modeling';
import { getMid } from 'diagram-js/lib/layout/LayoutUtil';
describe('features/modeling - CreateBehavior', function() {
var processDiagramXML = require('./CreateBehavior.bpmn');
beforeEach(bootstrapModeler(processDiagramXML, {
modules: [
coreModule,
modelingModule
]
}));
it('should ensure parent is participant', inject(
function(elementFactory, elementRegistry, modeling) {
// given
var lane = elementRegistry.get('Lane_1'),
participant = elementRegistry.get('Participant_1');
var task = elementFactory.createShape({
type: 'bpmn:Task'
});
// when
modeling.createShape(task, getMid(lane), lane);
// then
expect(task.parent).to.equal(participant);
}
));
});
================================================
FILE: test/spec/features/modeling/behavior/CreateParticipantBehaviorSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import coreModule from 'lib/core';
import createModule from 'diagram-js/lib/features/create';
import modelingModule from 'lib/features/modeling';
import {
getBBox
} from 'diagram-js/lib/util/Elements';
import { asTRBL } from 'diagram-js/lib/layout/LayoutUtil';
import { getDi } from 'lib/util/ModelUtil';
import {
createCanvasEvent as canvasEvent
} from '../../../../util/MockEvents';
describe('features/modeling - create participant', function() {
var testModules = [
coreModule,
createModule,
modelingModule
];
describe('process', function() {
describe('turning process into collaboration', function() {
var processDiagramXML = require('../../../../fixtures/bpmn/collaboration/process-empty.bpmn');
beforeEach(bootstrapModeler(processDiagramXML, { modules: testModules }));
var collaboration,
collaborationBo,
collaborationDi,
diRoot,
participant,
participant2,
participants,
participantBo,
participant2Bo,
participantDi,
participant2Di,
process,
processBo,
processDi;
beforeEach(inject(function(canvas, elementFactory) {
// given
process = canvas.getRootElement();
processBo = process.businessObject;
processDi = getDi(process);
diRoot = processDi.$parent;
participant = elementFactory.createParticipantShape({ x: 100, y: 100 });
participantBo = participant.businessObject;
participantDi = getDi(participant);
participant2 = elementFactory.createParticipantShape({ x: 100, y: 400 });
participant2Bo = participant2.businessObject;
participant2Di = getDi(participant2);
participants = [ participant, participant2 ];
}));
describe('creating one participant', function() {
beforeEach(inject(function(canvas, modeling) {
// when
modeling.createShape(participant, { x: 400, y: 225 }, process);
collaboration = canvas.getRootElement();
collaborationBo = collaboration.businessObject;
collaborationDi = getDi(collaboration);
}));
it('execute', function() {
// then
expect(participantBo.$parent).to.equal(collaborationBo);
expect(participantBo.processRef).to.equal(processBo);
expect(participantBo.processRef.id).to.equal(processBo.id);
expect(collaborationBo.$instanceOf('bpmn:Collaboration')).to.be.true;
expect(collaborationBo.$parent).to.equal(processBo.$parent);
expect(collaborationBo.participants).to.include(participantBo);
expect(participantDi.$parent).to.equal(collaborationDi);
expect(collaborationDi.$parent).to.equal(diRoot);
});
it('undo', inject(function(commandStack) {
// when
commandStack.undo();
// then
expect(participantBo.$parent).not.to.exist;
expect(participantBo.processRef).not.to.equal(processBo);
expect(collaborationBo.$parent).not.to.exist;
expect(collaborationBo.participants).not.to.include(participantBo);
expect(processDi.$parent).to.equal(diRoot);
}));
it('redo', inject(function(commandStack) {
// when
commandStack.undo();
commandStack.redo();
// then
expect(participantBo.$parent).to.equal(collaborationBo);
expect(participantBo.processRef).to.equal(processBo);
expect(participantBo.processRef.id).to.equal(processBo.id);
expect(collaborationBo.$instanceOf('bpmn:Collaboration')).to.be.true;
expect(collaborationBo.$parent).to.equal(processBo.$parent);
expect(collaborationBo.participants).to.include(participantBo);
expect(participantDi.$parent).to.equal(collaborationDi);
expect(collaborationDi.$parent).to.equal(diRoot);
}));
});
describe('creating two participants', function() {
beforeEach(inject(function(canvas, modeling) {
// when
modeling.createElements(participants, { x: 400, y: 375 }, process);
collaboration = canvas.getRootElement();
collaborationBo = collaboration.businessObject;
collaborationDi = getDi(collaboration);
}));
it('execute', function() {
// then
expect(participantBo.$parent).to.equal(collaborationBo);
expect(participantBo.processRef).to.equal(processBo);
expect(participant2Bo.$parent).to.equal(collaborationBo);
expect(participant2Bo.processRef).not.to.equal(processBo);
expect(collaborationBo.$instanceOf('bpmn:Collaboration')).to.be.true;
expect(collaborationBo.$parent).to.equal(processBo.$parent);
expect(collaborationBo.participants).to.include(participantBo);
expect(participantDi.$parent).to.equal(collaborationDi);
expect(participant2Di.$parent).to.equal(collaborationDi);
expect(collaborationDi.$parent).to.equal(diRoot);
});
it('undo', inject(function(commandStack) {
// when
commandStack.undo();
// then
expect(participantBo.$parent).not.to.exist;
expect(participantBo.processRef).not.to.equal(processBo);
expect(participant2Bo.$parent).not.to.exist;
expect(participant2Bo.processRef).not.to.equal(processBo);
expect(collaborationBo.$parent).not.to.exist;
expect(collaborationBo.participants).not.to.include(participantBo);
expect(collaborationBo.participants).not.to.include(participant2Bo);
expect(processDi.$parent).to.equal(diRoot);
}));
it('redo', inject(function(commandStack) {
// when
commandStack.undo();
commandStack.redo();
// then
expect(participantBo.$parent).to.equal(collaborationBo);
expect(participantBo.processRef).to.equal(processBo);
expect(participant2Bo.$parent).to.equal(collaborationBo);
expect(participant2Bo.processRef).not.to.equal(processBo);
expect(collaborationBo.$instanceOf('bpmn:Collaboration')).to.be.true;
expect(collaborationBo.$parent).to.equal(processBo.$parent);
expect(collaborationBo.participants).to.include(participantBo);
expect(participantDi.$parent).to.equal(collaborationDi);
expect(participant2Di.$parent).to.equal(collaborationDi);
expect(collaborationDi.$parent).to.equal(diRoot);
}));
});
});
describe('moving process children', function() {
var processDiagramXML = require('../../../../fixtures/bpmn/collaboration/process.bpmn');
beforeEach(bootstrapModeler(processDiagramXML, { modules: testModules }));
var collaboration,
collaborationDi,
participant,
process,
processDi;
beforeEach(inject(function(canvas, elementFactory, modeling) {
// given
process = canvas.getRootElement();
processDi = getDi(process);
participant = elementFactory.createParticipantShape();
// when
modeling.createShape(participant, { x: 350, y: 200 }, process);
collaboration = canvas.getRootElement();
collaborationDi = getDi(collaboration);
}));
it('execute', function() {
// then
expect(collaboration.children).to.have.length(4);
collaboration.children.forEach(function(child) {
var childDi = getDi(child);
expect(childDi.$parent).to.eql(collaborationDi);
expect(collaborationDi.planeElement).to.include(childDi);
});
expect(participant.children).to.have.length(5);
participant.children.forEach(function(child) {
var childDi = getDi(child);
expect(childDi.$parent).to.eql(collaborationDi);
expect(collaborationDi.planeElement).to.include(childDi);
});
});
it('undo', inject(function(commandStack) {
// when
commandStack.undo();
expect(process.children).to.have.length(8);
process.children.forEach(function(child) {
var childDi = getDi(child);
expect(childDi.$parent).to.eql(processDi);
expect(processDi.planeElement).to.include(childDi);
});
expect(participant.children.length).to.equal(0);
}));
it('should detach DI when turning process into collaboration', inject(function(modeling) {
// when
modeling.makeCollaboration();
// then
process.children.forEach(function(child) {
var childDi = getDi(child);
expect(childDi.$parent).not.to.exist;
expect(processDi.planeElement).not.to.include(childDi);
});
}));
});
describe('hovering process when creating first participant', function() {
var processDiagramXML = require('../../../../fixtures/bpmn/collaboration/process.bpmn');
beforeEach(bootstrapModeler(processDiagramXML, { modules: testModules }));
var participant,
process,
processGfx,
subProcess,
subProcessGfx;
beforeEach(inject(function(canvas, elementFactory, elementRegistry) {
// given
process = canvas.getRootElement();
processGfx = canvas.getGraphics(process);
participant = elementFactory.createParticipantShape();
subProcess = elementRegistry.get('SubProcess_1');
subProcessGfx = canvas.getGraphics(subProcess);
}));
it('should ensure hovering process', inject(function(create, dragging, eventBus) {
// given
create.start(canvasEvent({ x: 100, y: 100 }), participant);
var event = eventBus.createEvent({
element: subProcess,
gfx: subProcessGfx
});
// when
eventBus.fire('element.hover', event);
// then
expect(event.element).to.equal(process);
expect(event.gfx).to.equal(processGfx);
}));
it('should clean up', inject(function(create, dragging, eventBus) {
// given
create.start(canvasEvent({ x: 100, y: 100 }), participant);
// when
dragging.end();
// then
var event = eventBus.createEvent({
element: subProcess,
gfx: subProcessGfx
});
eventBus.fire('element.hover', event);
expect(event.element).to.equal(subProcess);
expect(event.gfx).to.equal(subProcessGfx);
}));
});
describe('fitting participant (default size)', function() {
var processDiagramXML = require('../../../../fixtures/bpmn/collaboration/process.bpmn');
beforeEach(bootstrapModeler(processDiagramXML, { modules: testModules }));
var participant,
process,
processGfx;
beforeEach(inject(function(canvas, create, dragging, elementFactory) {
// given
process = canvas.getRootElement();
processGfx = canvas.getGraphics(process);
participant = elementFactory.createParticipantShape();
create.start(canvasEvent({ x: 100, y: 100 }), participant);
dragging.hover({ element: process, gfx: processGfx });
}));
it('should fit participant', inject(function(elementFactory) {
// then
var defaultSize = elementFactory.getDefaultSize(participant);
expect(participant.width).to.equal(defaultSize.width);
expect(participant.height).to.equal(defaultSize.height);
}));
describe('create constraints', function() {
function expectBoundsWithin(inner, outer, padding) {
expect(inner.top >= outer.top + padding.top).to.be.true;
expect(inner.right <= outer.right - padding.right).to.be.true;
expect(inner.bottom <= outer.bottom - padding.bottom).to.be.true;
expect(inner.left >= outer.left + padding.left).to.be.true;
}
var padding = {
top: 20,
right: 20,
bottom: 20,
left: 50
};
[
{ x: 0, y: 0 },
{ x: 1000, y: 0 },
{ x: 0, y: 1000 },
{ x: 1000, y: 1000 }
].forEach(function(position) {
it('should constrain ' + JSON.stringify(position), inject(function(dragging) {
// when
dragging.move(canvasEvent(position));
dragging.end();
// then
expectBoundsWithin(
asTRBL(getBBox(participant.children)),
asTRBL(getBBox(participant)),
padding
);
}));
});
});
});
describe('fitting participant (only groups)', function() {
var processDiagramXML = require('../../../../fixtures/bpmn/collaboration/process-empty.bpmn');
beforeEach(bootstrapModeler(processDiagramXML, { modules: testModules }));
it('should fit participant', inject(
function(canvas, create, dragging, elementFactory, modeling) {
// given
var process = canvas.getRootElement(),
processGfx = canvas.getGraphics(process),
participant = elementFactory.createParticipantShape(),
groupElement = elementFactory.createShape({ type: 'bpmn:Group' });
modeling.createShape(groupElement, { x: 100, y: 100 }, process);
// when
create.start(canvasEvent({ x: 100, y: 100 }), participant);
dragging.hover({ element: process, gfx: processGfx });
// then
var defaultSize = elementFactory.getDefaultSize(participant);
expect(participant.width).to.equal(defaultSize.width);
expect(participant.height).to.equal(defaultSize.height);
}
));
});
});
describe('collaboration', function() {
var collaborationDiagramXML =
require('../../../../fixtures/bpmn/collaboration/collaboration-participant.bpmn');
beforeEach(bootstrapModeler(collaborationDiagramXML, { modules: testModules }));
var collaborationBo,
participant,
participantBo,
rootElement;
beforeEach(inject(function(canvas, elementFactory, modeling) {
// given
rootElement = canvas.getRootElement();
collaborationBo = rootElement.businessObject;
participant = elementFactory.createParticipantShape();
participantBo = participant.businessObject;
// when
modeling.createShape(participant, { x: 350, y: 500 }, rootElement);
}));
it('execute', function() {
// then
expect(rootElement.children).to.include(participant);
expect(participantBo.$parent).to.equal(collaborationBo);
expect(collaborationBo.participants).to.include(participantBo);
});
it('undo', inject(function(commandStack) {
// when
commandStack.undo();
// then
expect(rootElement.children).not.to.include(participant);
expect(participantBo.$parent).not.to.exist;
expect(collaborationBo.participants).not.to.include(participantBo);
}));
});
});
================================================
FILE: test/spec/features/modeling/behavior/DataInputAssociationBehavior.bpmn
================================================
DataObjectReference
================================================
FILE: test/spec/features/modeling/behavior/DataInputAssociationBehaviorSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import {
find
} from 'min-dash';
import modelingModule from 'lib/features/modeling';
describe('modeling/behavior - fix DataInputAssociation#targetRef', function() {
var diagramXML = require('./DataInputAssociationBehavior.bpmn');
beforeEach(bootstrapModeler(diagramXML, { modules: modelingModule }));
it('should add on connect', inject(function(modeling, elementRegistry) {
// given
var dataObjectShape = elementRegistry.get('DataObjectReference'),
taskShape = elementRegistry.get('Task_B');
// when
var newConnection = modeling.connect(dataObjectShape, taskShape, {
type: 'bpmn:DataInputAssociation'
});
var dataInputAssociation = newConnection.businessObject;
// then
expect(dataInputAssociation.targetRef).to.exist;
expect(dataInputAssociation.targetRef).to.eql(getTargetRefProp(taskShape));
}));
it('should remove on connect / undo', inject(function(modeling, elementRegistry, commandStack) {
// given
var dataObjectShape = elementRegistry.get('DataObjectReference'),
taskShape = elementRegistry.get('Task_B');
var newConnection = modeling.connect(dataObjectShape, taskShape, {
type: 'bpmn:DataInputAssociation'
});
var dataInputAssociation = newConnection.businessObject;
// when
commandStack.undo();
// then
expect(dataInputAssociation.targetRef).not.to.exist;
expect(getTargetRefProp(taskShape)).not.to.exist;
}));
it('should update on reconnectEnd', inject(function(modeling, elementRegistry) {
// given
var oldTarget = elementRegistry.get('Task_A'),
connection = elementRegistry.get('DataInputAssociation'),
dataInputAssociation = connection.businessObject,
newTarget = elementRegistry.get('Task_B');
// when
modeling.reconnectEnd(connection, newTarget, { x: newTarget.x, y: newTarget.y });
// then
expect(getTargetRefProp(oldTarget)).not.to.exist;
expect(dataInputAssociation.targetRef).to.exist;
expect(dataInputAssociation.targetRef).to.eql(getTargetRefProp(newTarget));
}));
it('should update on reconnectEnd / undo', inject(function(modeling, elementRegistry, commandStack) {
// given
var oldTarget = elementRegistry.get('Task_A'),
connection = elementRegistry.get('DataInputAssociation'),
dataInputAssociation = connection.businessObject,
newTarget = elementRegistry.get('Task_B');
modeling.reconnectEnd(connection, newTarget, { x: newTarget.x, y: newTarget.y });
// when
commandStack.undo();
// then
expect(getTargetRefProp(newTarget)).not.to.exist;
expect(dataInputAssociation.targetRef).to.exist;
expect(dataInputAssociation.targetRef).to.eql(getTargetRefProp(oldTarget));
}));
it('should unset on remove', inject(function(modeling, elementRegistry) {
// given
var oldTarget = elementRegistry.get('Task_A'),
connection = elementRegistry.get('DataInputAssociation'),
dataInputAssociation = connection.businessObject;
// when
modeling.removeElements([ connection ]);
// then
expect(getTargetRefProp(oldTarget)).not.to.exist;
expect(dataInputAssociation.targetRef).not.to.exist;
}));
it('should unset on remove / undo', inject(function(modeling, elementRegistry, commandStack) {
// given
var oldTarget = elementRegistry.get('Task_A'),
connection = elementRegistry.get('DataInputAssociation'),
dataInputAssociation = connection.businessObject;
modeling.removeElements([ connection ]);
// when
commandStack.undo();
// then
expect(dataInputAssociation.targetRef).to.exist;
expect(dataInputAssociation.targetRef).to.eql(getTargetRefProp(oldTarget));
}));
});
function getTargetRefProp(element) {
expect(element).to.exist;
var properties = element.businessObject.get('properties');
return find(properties, function(p) {
return p.name === '__targetRef_placeholder';
});
}
================================================
FILE: test/spec/features/modeling/behavior/DataObjectBehavior.create-data-association.bpmn
================================================
================================================
FILE: test/spec/features/modeling/behavior/DataObjectBehavior.data-object-reference.bpmn
================================================
================================================
FILE: test/spec/features/modeling/behavior/DataObjectBehavior.remove-data-association.bpmn
================================================
DataObjectReference_1
DataObjectReference_1
================================================
FILE: test/spec/features/modeling/behavior/DataObjectBehaviorSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import { is } from 'lib/util/ModelUtil';
import modelingModule from 'lib/features/modeling';
import coreModule from 'lib/core';
describe('features/modeling/behavior - data object', function() {
var testModules = [ coreModule, modelingModule ];
var rootShape;
describe('DataObjectReference', function() {
var processDiagramXML = require('./DataObjectBehavior.data-object-reference.bpmn');
beforeEach(bootstrapModeler(processDiagramXML, { modules: testModules }));
var subProcess1;
beforeEach(inject(function(canvas, elementRegistry) {
subProcess1 = elementRegistry.get('SubProcess_1');
rootShape = canvas.getRootElement();
}));
it('should create the corresponding DataObject', inject(function(modeling) {
// when
var dataObjectRefShape = modeling.createShape({ type: 'bpmn:DataObjectReference' },
{ x: 220, y: 220 }, rootShape);
var dataObject = dataObjectRefShape.businessObject.dataObjectRef;
// then
expect(dataObject).to.exist;
expect(is(dataObject, 'bpmn:DataObject')).to.be.true;
expect(dataObject.id).to.exist;
}));
it('should copy the isCollection property in DataObject if present', inject(function(modeling, copyPaste) {
// when
var dataObjectRefShape = modeling.createShape({ type: 'bpmn:DataObjectReference' },
{ x: 220, y: 220 }, rootShape);
dataObjectRefShape.businessObject.dataObjectRef.isCollection = true;
copyPaste.copy(dataObjectRefShape);
var pastedElements = copyPaste.paste({
element: rootShape,
point: {
x: 350,
y: 150
}
});
var dataObject = pastedElements[0].businessObject.dataObjectRef;
// then
expect(dataObject).to.exist;
expect(is(dataObject, 'bpmn:DataObject')).to.be.true;
expect(dataObject.id).to.exist;
expect(dataObject.isCollection).to.be.true;
}));
it('should create the corresponding DataObject / undo');
it('should have the right parents', inject(function(modeling) {
// when
var dataObjectRefShape1 = modeling.createShape({ type: 'bpmn:DataObjectReference' },
{ x: 220, y: 220 }, rootShape);
var dataObjectRefShape2 = modeling.createShape({ type: 'bpmn:DataObjectReference' },
{ x: 380, y: 220 }, subProcess1);
var dataObject1 = dataObjectRefShape1.businessObject.dataObjectRef;
var dataObject2 = dataObjectRefShape2.businessObject.dataObjectRef;
// then
expect(dataObject1.$parent.id).to.equal(rootShape.id);
expect(dataObjectRefShape1.parent.id).to.equal(rootShape.id);
expect(dataObject2.$parent.id).to.equal(subProcess1.id);
expect(dataObjectRefShape2.parent.id).to.equal(subProcess1.id);
}));
});
describe('create', function() {
var processDiagramXML = require('./DataObjectBehavior.create-data-association.bpmn');
beforeEach(bootstrapModeler(processDiagramXML, { modules: testModules }));
var dataObjectRefShape1,
taskShape;
beforeEach(inject(function(canvas, elementRegistry) {
rootShape = canvas.getRootElement();
dataObjectRefShape1 = elementRegistry.get('DataObjectReference_1');
taskShape = elementRegistry.get('Task_1');
}));
describe('dataOutputAssociation', function() {
it('should execute', inject(function(modeling) {
// when
var outputAssociation = modeling.connect(taskShape, dataObjectRefShape1);
var dataOutputAssociations = taskShape.businessObject.get('dataOutputAssociations');
// then
expect(dataOutputAssociations[0].$parent).to.equal(taskShape.businessObject);
expect(dataOutputAssociations).to.include(outputAssociation.businessObject);
expect(taskShape.businessObject.get('dataInputAssociations')).to.be.empty;
}));
it('should undo', inject(function(modeling, commandStack) {
// when
modeling.connect(taskShape, dataObjectRefShape1);
commandStack.undo();
// then
expect(taskShape.businessObject.get('dataOutputAssociations')).to.be.empty;
expect(taskShape.businessObject.get('dataInputAssociations')).to.be.empty;
}));
it('should redo', inject(function(modeling, commandStack) {
// when
var outputAssociation = modeling.connect(taskShape, dataObjectRefShape1);
commandStack.undo();
commandStack.redo();
var dataOutputAssociations = taskShape.businessObject.get('dataOutputAssociations');
// then
expect(dataOutputAssociations[0].$parent).to.equal(taskShape.businessObject);
expect(dataOutputAssociations).to.include(outputAssociation.businessObject);
expect(taskShape.businessObject.get('dataInputAssociations')).to.be.empty;
}));
});
describe('dataInputAssociation', function() {
it('should execute', inject(function(modeling) {
// when
var inputAssociation = modeling.connect(dataObjectRefShape1, taskShape);
var dataInputAssociations = taskShape.businessObject.get('dataInputAssociations');
// then
expect(dataInputAssociations[0].$parent).to.equal(taskShape.businessObject);
expect(dataInputAssociations).to.include(inputAssociation.businessObject);
expect(taskShape.businessObject.get('dataOutputAssociations')).to.be.empty;
}));
it('should undo', inject(function(modeling, commandStack) {
// when
modeling.connect(dataObjectRefShape1, taskShape);
commandStack.undo();
// then
expect(taskShape.businessObject.get('dataOutputAssociations')).to.be.empty;
expect(taskShape.businessObject.get('dataInputAssociations')).to.be.empty;
}));
it('should redo', inject(function(modeling, commandStack) {
// when
var inputAssociation = modeling.connect(dataObjectRefShape1, taskShape);
commandStack.undo();
commandStack.redo();
var dataInputAssociations = taskShape.businessObject.get('dataInputAssociations');
// then
expect(dataInputAssociations[0].$parent).to.equal(taskShape.businessObject);
expect(dataInputAssociations).to.include(inputAssociation.businessObject);
expect(taskShape.businessObject.get('dataOutputAssociations')).to.be.empty;
}));
});
});
describe('remove', function() {
var processDiagramXML = require('./DataObjectBehavior.remove-data-association.bpmn');
beforeEach(bootstrapModeler(processDiagramXML, { modules: testModules }));
var task1Shape,
task2Shape,
outputAssociation,
inputAssociation;
beforeEach(inject(function(canvas, elementRegistry) {
rootShape = canvas.getRootElement();
task1Shape = elementRegistry.get('Task_1');
task2Shape = elementRegistry.get('Task_2');
outputAssociation = elementRegistry.get('DataOutputAssociation_1');
inputAssociation = elementRegistry.get('DataInputAssociation_1');
}));
describe('DataOutputAssociation', function() {
it('should execute', inject(function(modeling) {
// when
modeling.removeConnection(outputAssociation);
// then
expect(task1Shape.businessObject.get('dataOutputAssociations')).to.be.empty;
expect(task1Shape.businessObject.get('dataInputAssociations')).to.be.empty;
}));
it('should undo', inject(function(modeling, commandStack) {
// when
modeling.removeConnection(outputAssociation);
commandStack.undo();
var dataOutputAssociations = task1Shape.businessObject.get('dataOutputAssociations');
// then
expect(dataOutputAssociations[0].$parent).to.equal(task1Shape.businessObject);
expect(dataOutputAssociations).to.be.include(outputAssociation.businessObject);
expect(task1Shape.businessObject.get('dataInputAssociations')).to.be.empty;
}));
it('should redo', inject(function(modeling, commandStack) {
// when
modeling.removeConnection(outputAssociation);
commandStack.undo();
commandStack.redo();
// then
expect(task1Shape.businessObject.get('dataOutputAssociations')).to.be.empty;
expect(task1Shape.businessObject.get('dataInputAssociations')).to.be.empty;
}));
});
describe('dataInputAssociation', function() {
it('should execute', inject(function(modeling) {
// when
modeling.removeConnection(inputAssociation);
// then
expect(task2Shape.businessObject.get('dataInputAssociations')).to.be.empty;
expect(task2Shape.businessObject.get('dataOutputAssociations')).to.be.empty;
}));
it('should undo', inject(function(modeling, commandStack) {
// when
modeling.removeConnection(inputAssociation);
commandStack.undo();
var dataInputAssociations = task2Shape.businessObject.get('dataInputAssociations');
// then
expect(dataInputAssociations[0].$parent).to.equal(task2Shape.businessObject);
expect(dataInputAssociations).to.include(inputAssociation.businessObject);
expect(task2Shape.businessObject.get('dataOutputAssociations')).to.be.empty;
}));
it('should redo', inject(function(modeling, commandStack) {
// when
modeling.removeConnection(inputAssociation);
commandStack.undo();
commandStack.redo();
// then
expect(task2Shape.businessObject.get('dataInputAssociations')).to.be.empty;
expect(task2Shape.businessObject.get('dataOutputAssociations')).to.be.empty;
}));
});
});
});
================================================
FILE: test/spec/features/modeling/behavior/DataStoreBehavior.bpmn
================================================
================================================
FILE: test/spec/features/modeling/behavior/DataStoreBehavior.collaboration.bpmn
================================================
================================================
FILE: test/spec/features/modeling/behavior/DataStoreBehavior.connect.bpmn
================================================
SequenceFlow_1
SequenceFlow_2
SequenceFlow_1
SequenceFlow_2
================================================
FILE: test/spec/features/modeling/behavior/DataStoreBehavior.empty-pool.bpmn
================================================
================================================
FILE: test/spec/features/modeling/behavior/DataStoreBehavior.process.bpmn
================================================
================================================
FILE: test/spec/features/modeling/behavior/DataStoreBehavior.remove-participant.bpmn
================================================
================================================
FILE: test/spec/features/modeling/behavior/DataStoreBehaviorSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import modelingModule from 'lib/features/modeling';
import coreModule from 'lib/core';
describe('features/modeling/behavior - data store', function() {
var testModules = [ coreModule, modelingModule ];
describe('create', function() {
var processDiagramXML = require('./DataStoreBehavior.bpmn');
beforeEach(bootstrapModeler(processDiagramXML, { modules: testModules }));
it('should create DataStoreReference on participant', inject(function(elementRegistry, modeling) {
// give
var participantElement = elementRegistry.get('Participant'),
participantBo = participantElement.businessObject,
processBo = participantBo.processRef;
// when
var dataStoreShape = modeling.createShape(
{ type: 'bpmn:DataStoreReference' },
{ x: 220, y: 220 },
participantElement
);
var dataStoreReference = dataStoreShape.businessObject;
// then
// reference correctly wired
expect(dataStoreReference.$parent).to.eql(processBo);
expect(processBo.flowElements).to.contain(dataStoreReference);
// no actual data store created
expect(dataStoreReference.dataStoreRef).not.to.exist;
}));
it('should create DataStoreReference on sub process', inject(function(elementRegistry, modeling) {
// give
var subProcessElement = elementRegistry.get('SubProcess'),
subProcessBo = subProcessElement.businessObject;
// when
var dataStoreShape = modeling.createShape(
{ type: 'bpmn:DataStoreReference' },
{ x: 420, y: 220 },
subProcessElement
);
var dataStoreReference = dataStoreShape.businessObject;
// then
// reference correctly wired
expect(dataStoreReference.$parent).to.eql(subProcessBo);
expect(subProcessBo.flowElements).to.contain(dataStoreReference);
// no actual data store created
expect(dataStoreReference.dataStoreRef).not.to.exist;
}));
it('should create DataStoreReference on collaboration', inject(function(elementRegistry, modeling) {
// give
var collaborationElement = elementRegistry.get('Collaboration'),
participantElement = elementRegistry.get('Participant'),
participantBo = participantElement.businessObject;
// when
var dataStoreShape = modeling.createShape(
{ type: 'bpmn:DataStoreReference' },
{ x: 420, y: 370 },
collaborationElement
);
var dataStoreReference = dataStoreShape.businessObject;
// then
// reference correctly wired
expect(dataStoreReference.$parent).to.eql(participantBo.processRef);
expect(participantBo.processRef.flowElements).to.contain(dataStoreReference);
// no actual data store created
expect(dataStoreReference.dataStoreRef).not.to.exist;
}));
describe('empty pool', function() {
var processDiagramXML = require('./DataStoreBehavior.empty-pool.bpmn');
beforeEach(bootstrapModeler(processDiagramXML, { modules: testModules }));
it('should create DataStoreReference on collaboration if first participant is an empty pool',
inject(function(elementRegistry, modeling) {
// give
var collaboration = elementRegistry.get('Collaboration'),
participantWithProcess = elementRegistry.get('Participant_2').businessObject;
// when
var dataStoreShape = modeling.createShape(
{ type: 'bpmn:DataStoreReference' },
{ x: 420, y: 370 },
collaboration
);
var dataStoreReference = dataStoreShape.businessObject;
// then
// reference correctly wired
expect(dataStoreReference.$parent).to.exist;
expect(dataStoreReference.$parent).to.eql(participantWithProcess.processRef);
expect(participantWithProcess.processRef.flowElements).to.contain(dataStoreReference);
// no actual data store created
expect(dataStoreReference.dataStoreRef).not.to.exist;
})
);
});
});
describe('move', function() {
var processDiagramXML = require('./DataStoreBehavior.bpmn');
beforeEach(bootstrapModeler(processDiagramXML, { modules: testModules }));
it('should move DataStoreReference to Participant', inject(function(elementRegistry, modeling) {
// give
var participantElement = elementRegistry.get('Participant'),
participantBo = participantElement.businessObject,
dataStoreReference = elementRegistry.get('DataStoreReference'),
dataStoreReferenceBo = dataStoreReference.businessObject;
// when
modeling.moveElements([ dataStoreReference ], { x: -200, y: 0 }, participantElement);
// then
expect(dataStoreReference.parent).to.eql(participantElement);
expect(dataStoreReferenceBo.$parent).to.eql(participantBo.processRef);
}));
it('should move DataStoreReference from partipant to Collaboration keeping parent participant', inject(
function(elementRegistry, modeling) {
// give
var collaborationElement = elementRegistry.get('Collaboration'),
participant2Element = elementRegistry.get('Participant_2'),
participant2Bo = participant2Element.businessObject,
dataStoreReference2 = elementRegistry.get('DataStoreReference_2'),
dataStoreReference2Bo = dataStoreReference2.businessObject;
// when
modeling.moveElements([ dataStoreReference2 ], { x: 0, y: 250 }, collaborationElement);
// then
expect(dataStoreReference2.parent).to.eql(collaborationElement);
expect(dataStoreReference2Bo.$parent).to.eql(participant2Bo.processRef);
})
);
it('should move DataStoreReference from subprocess to Collaboration keeping parent participant', inject(
function(elementRegistry, modeling) {
// give
var collaborationElement = elementRegistry.get('Collaboration'),
participantElement = elementRegistry.get('Participant'),
participantBo = participantElement.businessObject,
dataStoreReference = elementRegistry.get('DataStoreReference'),
dataStoreReferenceBo = dataStoreReference.businessObject;
// when
modeling.moveElements([ dataStoreReference ], { x: 0, y: 250 }, collaborationElement);
// then
expect(dataStoreReference.parent).to.eql(collaborationElement);
expect(dataStoreReferenceBo.$parent).to.eql(participantBo.processRef);
})
);
it('should move without changing parent', inject(
function(elementRegistry, modeling) {
// give
var collaborationElement = elementRegistry.get('Collaboration'),
participant2Element = elementRegistry.get('Participant_2'),
participant2Bo = participant2Element.businessObject,
dataStoreReference3 = elementRegistry.get('DataStoreReference_3'),
dataStoreReference3Bo = dataStoreReference3.businessObject;
// when
modeling.moveElements([ dataStoreReference3 ], { x: 50, y: 0 }, collaborationElement);
// then
expect(dataStoreReference3.parent).to.eql(collaborationElement);
expect(dataStoreReference3Bo.$parent).to.eql(participant2Bo.processRef);
})
);
});
describe('connect', function() {
var processDiagramXML = require('./DataStoreBehavior.connect.bpmn');
beforeEach(bootstrapModeler(processDiagramXML, { modules: testModules }));
describe('dataOutputAssociation', function() {
it('should execute', inject(function(elementRegistry, modeling) {
// given
var taskShape = elementRegistry.get('Task'),
dataStoreRefShape = elementRegistry.get('DataStoreReference');
// when
var outputAssociation = modeling.connect(taskShape, dataStoreRefShape);
var dataOutputAssociations = taskShape.businessObject.get('dataOutputAssociations');
// then
expect(dataOutputAssociations).to.eql([ outputAssociation.businessObject ]);
expect(outputAssociation.businessObject.$parent).to.eql(taskShape.businessObject);
expect(taskShape.businessObject.get('dataInputAssociations')).to.be.empty;
}));
it('should undo', inject(function(elementRegistry, modeling, commandStack) {
// given
var taskShape = elementRegistry.get('Task'),
dataStoreRefShape = elementRegistry.get('DataStoreReference');
// when
modeling.connect(taskShape, dataStoreRefShape);
commandStack.undo();
// then
expect(taskShape.businessObject.get('dataOutputAssociations')).to.be.empty;
expect(taskShape.businessObject.get('dataInputAssociations')).to.be.empty;
}));
it('should redo', inject(function(elementRegistry, modeling, commandStack) {
// given
var taskShape = elementRegistry.get('Task'),
dataStoreRefShape = elementRegistry.get('DataStoreReference');
// when
var outputAssociation = modeling.connect(taskShape, dataStoreRefShape);
commandStack.undo();
commandStack.redo();
var dataOutputAssociations = taskShape.businessObject.get('dataOutputAssociations');
// then
expect(dataOutputAssociations).to.eql([ outputAssociation.businessObject ]);
expect(outputAssociation.businessObject.$parent).to.eql(taskShape.businessObject);
expect(taskShape.businessObject.get('dataInputAssociations')).to.be.empty;
}));
});
describe('dataInputAssociation', function() {
it('should execute', inject(function(elementRegistry, modeling) {
// given
var taskShape = elementRegistry.get('Task'),
dataStoreRefShape = elementRegistry.get('DataStoreReference');
// when
var inputAssociation = modeling.connect(dataStoreRefShape, taskShape);
var dataInputAssociations = taskShape.businessObject.get('dataInputAssociations');
// then
expect(dataInputAssociations).to.eql([ inputAssociation.businessObject ]);
expect(inputAssociation.businessObject.$parent).to.eql(taskShape.businessObject);
expect(taskShape.businessObject.get('dataOutputAssociations')).to.be.empty;
}));
it('should undo', inject(function(elementRegistry, modeling, commandStack) {
// given
var taskShape = elementRegistry.get('Task'),
dataStoreRefShape = elementRegistry.get('DataStoreReference');
// when
modeling.connect(dataStoreRefShape, taskShape);
commandStack.undo();
// then
expect(taskShape.businessObject.get('dataOutputAssociations')).to.be.empty;
expect(taskShape.businessObject.get('dataInputAssociations')).to.be.empty;
}));
it('should redo', inject(function(elementRegistry, modeling, commandStack) {
// given
var taskShape = elementRegistry.get('Task'),
dataStoreRefShape = elementRegistry.get('DataStoreReference');
// when
var inputAssociation = modeling.connect(dataStoreRefShape, taskShape);
commandStack.undo();
commandStack.redo();
var dataInputAssociations = taskShape.businessObject.get('dataInputAssociations');
// then
expect(dataInputAssociations[0].$parent).to.equal(taskShape.businessObject);
expect(dataInputAssociations).to.include(inputAssociation.businessObject);
expect(taskShape.businessObject.get('dataOutputAssociations')).to.be.empty;
}));
});
});
describe('process', function() {
var processDiagramXML = require('./DataStoreBehavior.process.bpmn');
beforeEach(bootstrapModeler(processDiagramXML, { modules: testModules }));
it('should not update parent on subprocess delete', inject(
function(elementRegistry, eventBus, modeling) {
// given
var spy = sinon.spy();
eventBus.on('commandStack.dataStore.updateContainment.execute', spy);
var subProcessElement = elementRegistry.get('SubProcess');
// when
modeling.removeShape(subProcessElement);
// then
expect(spy).to.not.have.been.called;
}
));
});
describe('collaboration', function() {
describe('update parent on participant removed', function() {
var processDiagramXML = require('./DataStoreBehavior.remove-participant.bpmn');
var dataStoreReferenceBo,
participantBo,
participant2Bo;
beforeEach(bootstrapModeler(processDiagramXML, { modules: testModules }));
beforeEach(inject(function(elementRegistry, modeling) {
// given
var participantElement = elementRegistry.get('Participant'),
participant2Element = elementRegistry.get('Participant_2'),
dataStoreReference = elementRegistry.get('DataStoreReference');
dataStoreReferenceBo = dataStoreReference.businessObject;
participantBo = participantElement.businessObject;
participant2Bo = participant2Element.businessObject;
// when
modeling.removeShape(participantElement);
}));
it('should do', function() {
// then
expect(dataStoreReferenceBo.$parent).to.eql(participant2Bo.processRef);
});
it('should undo', inject(function(commandStack) {
// when
commandStack.undo();
// then
expect(dataStoreReferenceBo.$parent).to.eql(participantBo.processRef);
}));
it('should redo', inject(function(commandStack) {
// when
commandStack.undo();
commandStack.redo();
// then
expect(dataStoreReferenceBo.$parent).to.eql(participant2Bo.processRef);
}));
});
describe('collaboration -> process', function() {
var processDiagramXML = require('./DataStoreBehavior.collaboration.bpmn');
var dataStoreShape,
participant;
beforeEach(bootstrapModeler(processDiagramXML, { modules: testModules }));
beforeEach(inject(function(elementRegistry, modeling) {
dataStoreShape = elementRegistry.get('DataStoreReference');
participant = elementRegistry.get('Participant');
// when
modeling.removeShape(participant);
}));
it('should do', inject(function(canvas) {
var rootElement = canvas.getRootElement();
// then
expect(dataStoreShape.businessObject.$parent).to.eql(rootElement.businessObject);
}));
it('should undo', inject(function(canvas, commandStack) {
// when
commandStack.undo();
// then
expect(dataStoreShape.businessObject.$parent).to.eql(participant.businessObject.processRef);
}));
it('should redo', inject(function(canvas, commandStack) {
var rootElement = canvas.getRootElement();
// when
commandStack.undo();
commandStack.redo();
// then
expect(dataStoreShape.businessObject.$parent).to.eql(rootElement.businessObject);
}));
});
describe('process -> collaboration', function() {
var processDiagramXML = require('./DataStoreBehavior.process.bpmn');
var dataStoreShape,
participant,
process;
beforeEach(bootstrapModeler(processDiagramXML, { modules: testModules }));
beforeEach(inject(function(canvas, elementRegistry, modeling) {
process = canvas.getRootElement();
dataStoreShape = elementRegistry.get('DataStoreReference');
// when
participant = modeling.createShape(
{ type: 'bpmn:Participant' },
{ x: 200, y: 200 },
process
);
}));
it('should do', function() {
// then
expect(dataStoreShape.businessObject.$parent).to.eql(participant.businessObject.processRef);
});
it('should undo', inject(function(commandStack) {
// when
commandStack.undo();
// then
expect(dataStoreShape.businessObject.$parent).to.eql(process.businessObject);
}));
it('should redo', inject(function(commandStack) {
// when
commandStack.undo();
commandStack.redo();
// then
expect(dataStoreShape.businessObject.$parent).to.eql(participant.businessObject.processRef);
}));
});
});
});
================================================
FILE: test/spec/features/modeling/behavior/DetachEventBehavior.bpmn
================================================
bar
SequenceFlow_2
SequenceFlow_2
================================================
FILE: test/spec/features/modeling/behavior/DetachEventBehaviorSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import coreModule from 'lib/core';
import modelingModule from 'lib/features/modeling';
import { getBusinessObject } from '../../../../../lib/util/ModelUtil';
describe('features/modeling/behavior - detach events', function() {
var testModules = [
coreModule,
modelingModule
];
var detachEventBehaviorXML = require('./DetachEventBehavior.bpmn');
beforeEach(bootstrapModeler(detachEventBehaviorXML, { modules: testModules }));
describe('basics', function() {
describe('create', function() {
it('should replace', inject(function(elementFactory, elementRegistry, modeling) {
// given
var process = elementRegistry.get('Process_1');
var boundaryEvent = elementFactory.createShape({ type: 'bpmn:BoundaryEvent' });
// when
var intermediateThrowEvent = modeling.createElements(
boundaryEvent, { x: 200, y: 100 }, process
)[0];
// then
var intermediateThrowEventBo = getBusinessObject(intermediateThrowEvent);
expect(intermediateThrowEventBo.$type).to.equal('bpmn:IntermediateThrowEvent');
}));
it('should NOT replace', inject(function(elementFactory, elementRegistry, modeling) {
// given
var task = elementRegistry.get('Task_1'),
taskBo = getBusinessObject(task);
var boundaryEvent = elementFactory.createShape({ type: 'bpmn:BoundaryEvent' }),
boundaryEventBo = getBusinessObject(boundaryEvent);
// when
boundaryEvent = modeling.createElements(
boundaryEvent, { x: 100, y: 60 }, task, { attach: true }
)[0];
// then
expect(boundaryEventBo.$type).to.equal('bpmn:BoundaryEvent');
expect(boundaryEventBo.attachedToRef).to.equal(taskBo);
}));
it('should copy properties', inject(
function(bpmnFactory, elementFactory, elementRegistry, modeling) {
// given
var process = elementRegistry.get('Process_1');
var boundaryEventBo = bpmnFactory.create('bpmn:BoundaryEvent', {
name: 'foo'
});
var documentation = bpmnFactory.create('bpmn:Documentation', {
text: 'bar'
});
boundaryEventBo.documentation = [ documentation ];
documentation.$parent = boundaryEventBo;
var boundaryEvent = elementFactory.createShape({
type: 'bpmn:BoundaryEvent',
businessObject: boundaryEventBo
});
// when
var intermediateThrowEvent = modeling.createElements(
boundaryEvent, { x: 200, y: 100 }, process
)[0];
// then
var intermediateThrowEventBo = getBusinessObject(intermediateThrowEvent);
expect(intermediateThrowEventBo.name).to.equal('foo');
expect(intermediateThrowEventBo.documentation).to.have.lengthOf(1);
expect(intermediateThrowEventBo.documentation[0].text).to.equal('bar');
}
));
});
describe('move', function() {
it('should replace', inject(function(elementRegistry, modeling) {
// given
var process = elementRegistry.get('Process_1'),
boundaryEvent = elementRegistry.get('BoundaryEvent_1');
// when
modeling.moveElements([ boundaryEvent ], { x: 0, y: 100 }, process);
// then
var intermediateThrowEvent = elementRegistry.get('BoundaryEvent_1'),
intermediateThrowEventBo = getBusinessObject(intermediateThrowEvent);
expect(intermediateThrowEvent).to.exist;
expect(intermediateThrowEventBo.$type).to.equal('bpmn:IntermediateThrowEvent');
expect(intermediateThrowEventBo.attachedToRef).not.to.exist;
}));
it('should NOT replace', inject(function(elementRegistry, modeling) {
// given
var task = elementRegistry.get('Task_1'),
taskBo = getBusinessObject(task),
boundaryEvent = elementRegistry.get('BoundaryEvent_1');
// when
modeling.moveElements([ boundaryEvent ], { x: 0, y: -80 }, task, { attach: true });
// then
boundaryEvent = elementRegistry.get('BoundaryEvent_1');
var boundaryEventBo = getBusinessObject(boundaryEvent);
expect(boundaryEventBo.$type).to.equal('bpmn:BoundaryEvent');
expect(boundaryEventBo.attachedToRef).to.equal(taskBo);
}));
it('should replace multiple', inject(function(canvas, elementRegistry, modeling) {
// given
var boundaryEvent = elementRegistry.get('BoundaryEvent_1'),
boundaryConditionalEvent = elementRegistry.get('BoundaryConditionalEvent'),
root = canvas.getRootElement();
// when
modeling.moveElements([ boundaryEvent, boundaryConditionalEvent ], { x: 0, y: 200 }, root);
// then
var intermediateThrowEvent = elementRegistry.get('BoundaryEvent_1'),
intermediateCatchEvent = elementRegistry.get('BoundaryConditionalEvent'),
intermediateThrowEventBo = getBusinessObject(intermediateThrowEvent),
intermediateCatchEventBo = getBusinessObject(intermediateCatchEvent);
expect(intermediateCatchEventBo.$type).to.equal('bpmn:IntermediateCatchEvent');
expect(intermediateCatchEventBo.attachedToRef).not.to.exist;
expect(intermediateThrowEventBo.$type).to.equal('bpmn:IntermediateThrowEvent');
expect(intermediateThrowEventBo.attachedToRef).not.to.exist;
}));
describe('properties', function() {
it('should copy properties', inject(function(elementRegistry, modeling) {
// given
var process = elementRegistry.get('Process_1'),
boundaryEvent = elementRegistry.get('BoundaryEvent_1');
// when
modeling.moveElements([ boundaryEvent ], { x: 0, y: 100 }, process);
// then
var intermediateThrowEvent = elementRegistry.get('BoundaryEvent_1'),
intermediateThrowEventBo = getBusinessObject(intermediateThrowEvent);
expect(intermediateThrowEventBo.name).to.equal('foo');
expect(intermediateThrowEventBo.documentation).to.have.lengthOf(1);
expect(intermediateThrowEventBo.documentation[0].text).to.equal('bar');
}));
describe('event definitions', function() {
var ids = [
'BoundaryConditionalEvent',
'BoundaryMessageEvent',
'BoundarySignalEvent',
'BoundaryTimerEvent'
];
ids.forEach(function(id) {
it('should copy event definition', inject(function(elementRegistry, modeling) {
// given
var process = elementRegistry.get('Process_1'),
boundaryEvent = elementRegistry.get(id),
boundaryEventBo = getBusinessObject(boundaryEvent),
eventDefinitions = boundaryEventBo.eventDefinitions;
// when
modeling.moveElements([ boundaryEvent ], { x: 0, y: 100 }, process);
// then
var intermediateCatchEvent = elementRegistry.get(id),
intermediateCatchEventBo = getBusinessObject(intermediateCatchEvent);
expect(intermediateCatchEventBo.$type).to.equal('bpmn:IntermediateCatchEvent');
expect(intermediateCatchEventBo.eventDefinitions).to.jsonEqual(eventDefinitions, skipId);
}));
});
it('should NOT create event definition', inject(function(elementRegistry, modeling) {
// given
var process = elementRegistry.get('Process_1'),
boundaryEvent = elementRegistry.get('BoundaryEvent_1'),
boundaryEventBo = getBusinessObject(boundaryEvent),
eventDefinitions = boundaryEventBo.eventDefinitions;
// when
modeling.moveElements([ boundaryEvent ], { x: 0, y: 100 }, process);
// then
var intermediateThrowEvent = elementRegistry.get('BoundaryEvent_1'),
intermediateThrowEventBo = getBusinessObject(intermediateThrowEvent);
expect(intermediateThrowEventBo.$type).to.equal('bpmn:IntermediateThrowEvent');
expect(intermediateThrowEventBo.eventDefinitions).to.jsonEqual(eventDefinitions, skipId);
}));
});
});
});
});
describe('connections', function() {
it('should NOT remove outgoing connection', inject(function(elementRegistry, modeling) {
// given
var process = elementRegistry.get('Process_1'),
endEvent = elementRegistry.get('EndEvent_1'),
boundaryEvent = elementRegistry.get('BoundaryEvent_1');
// when
modeling.moveElements([ boundaryEvent ], { x: 0, y: 100 }, process);
// then
var intermediateThrowEvent = elementRegistry.get('BoundaryEvent_1');
expect(intermediateThrowEvent.outgoing).to.have.lengthOf(1);
expect(endEvent.incoming).to.have.lengthOf(1);
}));
it('should lay out connection once', inject(function(eventBus, elementRegistry, modeling) {
// given
var process = elementRegistry.get('Process_1'),
boundaryEvent = elementRegistry.get('BoundaryEvent_1');
var layoutSpy = sinon.spy();
eventBus.on('commandStack.connection.layout.execute', layoutSpy);
// when
modeling.moveElements([ boundaryEvent ], { x: 0, y: 100 }, process);
// then
expect(layoutSpy).to.be.calledOnce;
}));
});
describe('labels', function() {
it('should NOT replace', inject(function(elementRegistry, modeling) {
var process = elementRegistry.get('Process_1'),
boundaryEvent = elementRegistry.get('BoundaryEvent_1'),
label = boundaryEvent.label;
// when
modeling.moveElements([ label ], { x: 0, y: 100 }, process);
// then
expect(elementRegistry.get('BoundaryEvent_1')).to.equal(boundaryEvent);
}));
});
});
// helpers //////////
function skipId(key, value) {
if (key === 'id') {
return;
}
return value;
}
================================================
FILE: test/spec/features/modeling/behavior/DropOnFlowBehavior.bpmn
================================================
SequenceFlow_1
SequenceFlow_3
SequenceFlow_1
SequenceFlow_4
SequenceFlow_2
SequenceFlow_2
SequenceFlow_3
SequenceFlow_4
SequenceFlow_D
SequenceFlow_D
SequenceFlow_F
SequenceFlow_F
================================================
FILE: test/spec/features/modeling/behavior/DropOnFlowBehaviorSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import {
flatten
} from 'min-dash';
import coreModule from 'lib/core';
import moveModule from 'diagram-js/lib/features/move';
import modelingModule from 'lib/features/modeling';
import {
createCanvasEvent as canvasEvent
} from '../../../../util/MockEvents';
import { getMid } from 'diagram-js/lib/layout/LayoutUtil';
describe('modeling/behavior - drop on connection', function() {
var diagramXML = require('./DropOnFlowBehavior.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: [
moveModule,
modelingModule,
coreModule
]
}));
describe('execution', function() {
describe('create', function() {
describe('should connect start -> target -> end', function() {
it('connection middle', inject(
function(modeling, elementRegistry, elementFactory) {
// given
var intermediateThrowEvent = elementFactory.createShape({
type: 'bpmn:IntermediateThrowEvent'
});
var startEvent = elementRegistry.get('StartEvent'),
sequenceFlow = elementRegistry.get('SequenceFlow_1'),
task = elementRegistry.get('Task_1');
var originalWaypoints = sequenceFlow.waypoints;
var dropPosition = { x: 340, y: 120 }; // first bendpoint
// when
var newShape = modeling.createShape(
intermediateThrowEvent,
dropPosition,
sequenceFlow
);
// then
var targetConnection = newShape.outgoing[0];
// new incoming connection
expect(newShape.incoming).to.have.length(1);
expect(newShape.incoming[0]).to.eql(sequenceFlow);
// new outgoing connection
expect(newShape.outgoing).to.have.length(1);
expect(targetConnection).to.exist;
expect(targetConnection.type).to.equal('bpmn:SequenceFlow');
expect(startEvent.outgoing[0]).to.equal(newShape.incoming[0]);
expect(task.incoming[1]).to.equal(newShape.outgoing[0]);
// split target at insertion point
expect(sequenceFlow).to.have.waypoints(flatten([
originalWaypoints.slice(0, 1),
{ x: 322, y: 120 }
]));
expect(sequenceFlow).to.have.endDocking(dropPosition);
expect(targetConnection).to.have.waypoints(flatten([
{ x: 340, y: 138 },
originalWaypoints.slice(2)
]));
expect(targetConnection).to.have.startDocking(dropPosition);
}
));
it('close to source', inject(
function(modeling, elementRegistry, elementFactory) {
// given
var dropElement = elementFactory.createShape({
type: 'bpmn:IntermediateThrowEvent'
});
var start = elementRegistry.get('Gateway_C'),
flow = elementRegistry.get('SequenceFlow_F'),
end = elementRegistry.get('Task_B');
var originalWaypoints = flow.waypoints.slice();
var dropPosition = { x: 495, y: 540 }; // overlapping source
// when
var newShape = modeling.createShape(
dropElement,
dropPosition,
flow
);
// then
var targetConnection = newShape.outgoing[0];
// new incoming connection
expect(newShape.incoming).to.have.length(1);
expect(newShape.incoming[0]).to.equal(flow);
// new outgoing connection
expect(newShape.outgoing).to.have.length(1);
expect(targetConnection).to.exist;
expect(targetConnection.type).to.equal('bpmn:SequenceFlow');
expect(start.outgoing[0]).to.equal(newShape.incoming[0]);
expect(end.incoming[0]).to.equal(newShape.outgoing[0]);
// split target at insertion point
expect(flow).to.have.waypoints(flatten([
originalWaypoints.slice(0, 1),
[
{ x: 477, y: 540 }
]
]));
expect(flow).to.have.endDocking(dropPosition);
expect(targetConnection).to.have.waypoints(flatten([
{ x: 513, y: 540 },
originalWaypoints.slice(1)
]));
expect(targetConnection).to.have.startDocking(dropPosition);
}
));
it('close to target', inject(
function(modeling, elementRegistry, elementFactory) {
// given
var dropElement = elementFactory.createShape({
type: 'bpmn:IntermediateThrowEvent'
});
var start = elementRegistry.get('Gateway_C'),
flow = elementRegistry.get('SequenceFlow_F'),
end = elementRegistry.get('Task_B');
var originalWaypoints = flow.waypoints.slice();
var dropPosition = { x: 625, y: 540 }; // overlapping target
// when
var newShape = modeling.createShape(
dropElement,
dropPosition,
flow
);
// then
var targetConnection = newShape.outgoing[0];
// new incoming connection
expect(newShape.incoming).to.have.length(1);
expect(newShape.incoming[0]).to.equal(flow);
// new outgoing connection
expect(newShape.outgoing).to.have.length(1);
expect(targetConnection).to.exist;
expect(targetConnection.type).to.equal('bpmn:SequenceFlow');
expect(start.outgoing[0]).to.equal(newShape.incoming[0]);
expect(end.incoming[0]).to.equal(newShape.outgoing[0]);
// split target at insertion point
expect(flow).to.have.waypoints(flatten([
originalWaypoints.slice(0, 1),
[
{ x: 607, y: 540 }
]
]));
expect(flow).to.have.endDocking(dropPosition);
expect(targetConnection).to.have.waypoints(flatten([
{ x: 643, y: 540 },
originalWaypoints.slice(1)
]));
expect(targetConnection).to.have.startDocking(dropPosition);
}
));
});
it('should connect start -> target', inject(
function(modeling, elementRegistry, elementFactory) {
// given
var endEventShape = elementFactory.createShape({ type: 'bpmn:EndEvent' });
var sequenceFlow = elementRegistry.get('SequenceFlow_1');
var originalWaypoints = sequenceFlow.waypoints;
var dropPosition = { x: 340, y: 120 }; // first bendpoint
// when
var newShape = modeling.createShape(
endEventShape,
dropPosition,
sequenceFlow
);
// then
// new incoming connection
expect(newShape.incoming).to.have.length(1);
expect(newShape.incoming[0]).to.eql(sequenceFlow);
// no outgoing edges
expect(newShape.outgoing).to.have.length(0);
// split target at insertion point
expect(sequenceFlow).to.have.waypoints(flatten([
originalWaypoints.slice(0, 1),
{ x: 322, y: 120 }
]));
}
));
it('should connect target -> end', inject(
function(modeling, elementRegistry, elementFactory) {
// given
var startEventShape = elementFactory.createShape({
type: 'bpmn:StartEvent'
});
var sequenceFlow = elementRegistry.get('SequenceFlow_1');
var originalWaypoints = sequenceFlow.waypoints;
var dropPosition = { x: 340, y: 120 }; // first bendpoint
// when
var newShape = modeling.createShape(
startEventShape,
dropPosition,
sequenceFlow
);
// then
// no incoming connection
expect(newShape.incoming).to.have.length(0);
// no outgoing edges
expect(newShape.outgoing).to.have.length(1);
expect(newShape.outgoing[0]).to.eql(sequenceFlow);
// split target at insertion point
expect(sequenceFlow).to.have.waypoints(flatten([
{ x: 340, y: 138 },
originalWaypoints.slice(2)
]));
}
));
it('should connect start -> target -> end (with bendpointBefore inside bbox)', inject(
function(modeling, elementRegistry, elementFactory) {
// given
var taskShape = elementFactory.createShape({ type: 'bpmn:Task' }),
sequenceFlow = elementRegistry.get('SequenceFlow_1'),
originalWaypoints = sequenceFlow.waypoints,
dropPosition = { x: 340, y: 145 }; // 25 pixels below bendpoint
// when
modeling.createShape(taskShape, dropPosition, sequenceFlow);
// then
// split target but don't keep insertion point
expect(sequenceFlow).to.have.waypoints(flatten([
originalWaypoints.slice(0, 1),
{ x: 290, y: 120 }
]));
expect(sequenceFlow).to.have.endDocking({ x: 340, y: 120 });
}
));
it('should connect start -> target -> end (with bendpointAfter inside bbox)', inject(
function(modeling, elementRegistry, elementFactory) {
// given
var taskShape = elementFactory.createShape({ type: 'bpmn:Task' }),
sequenceFlow = elementRegistry.get('SequenceFlow_1'),
originalWaypoints = sequenceFlow.waypoints,
dropPosition = { x: 340, y: 280 }; // 25 pixels above bendpoint
// when
var newShape = modeling.createShape(taskShape, dropPosition, sequenceFlow),
targetConnection = newShape.outgoing[0];
// then
// split target but don't keep insertion point
expect(targetConnection).to.have.waypoints(flatten([
{ x: 390, y: 299 },
originalWaypoints.slice(3)
]));
expect(targetConnection).to.have.startDocking({ x: 340, y: 299 });
}
));
it('should handle shape created with bounds', inject(
function(elementFactory, elementRegistry, modeling) {
// given
var intermediateThrowEvent = elementFactory.createShape({
type: 'bpmn:IntermediateThrowEvent'
});
var startEvent = elementRegistry.get('StartEvent'),
sequenceFlow = elementRegistry.get('SequenceFlow_1'),
task = elementRegistry.get('Task_1');
var originalWaypoints = sequenceFlow.waypoints;
var dropBounds = { x: 322, y: 102, width: 36, height: 36 }; // first bendpoint
// when
var newShape = modeling.createShape(
intermediateThrowEvent,
dropBounds,
sequenceFlow
);
// then
var targetConnection = newShape.outgoing[0];
// new incoming connection
expect(newShape.incoming).to.have.length(1);
expect(newShape.incoming[0]).to.eql(sequenceFlow);
// new outgoing connection
expect(newShape.outgoing).to.have.length(1);
expect(targetConnection).to.exist;
expect(targetConnection.type).to.equal('bpmn:SequenceFlow');
expect(startEvent.outgoing[0]).to.equal(newShape.incoming[0]);
expect(task.incoming[1]).to.equal(newShape.outgoing[0]);
// split target at insertion point
expect(sequenceFlow).to.have.waypoints(flatten([
originalWaypoints.slice(0, 1),
{ x: 322, y: 120 }
]));
expect(sequenceFlow).to.have.endDocking(getMid(dropBounds));
expect(targetConnection).to.have.waypoints(flatten([
{ x: 340, y: 138 },
originalWaypoints.slice(2)
]));
expect(targetConnection).to.have.startDocking(getMid(dropBounds));
}
));
});
describe('move', function() {
beforeEach(inject(function(dragging) {
dragging.setOptions({ manual: true });
}));
it('should connect start -> target -> end', inject(
function(dragging, move, elementRegistry, selection) {
// given
var intermediateThrowEvent = elementRegistry.get('IntermediateThrowEvent_foo');
var startEvent = elementRegistry.get('StartEvent'),
sequenceFlow = elementRegistry.get('SequenceFlow_1'),
sequenceFlowGfx = elementRegistry.getGraphics(sequenceFlow),
task = elementRegistry.get('Task_1');
var originalWaypoints = sequenceFlow.waypoints;
// when
selection.select(intermediateThrowEvent);
move.start(canvasEvent({ x: 0, y: 0 }), intermediateThrowEvent);
dragging.hover({
element: sequenceFlow,
gfx: sequenceFlowGfx
});
dragging.move(canvasEvent({ x: 149, y: 0 }));
dragging.end();
// then
var targetConnection = intermediateThrowEvent.outgoing[0];
// new incoming connection
expect(intermediateThrowEvent.incoming).to.have.length(1);
expect(intermediateThrowEvent.incoming[0]).to.eql(sequenceFlow);
// new outgoing connection
expect(intermediateThrowEvent.outgoing).to.have.length(1);
expect(targetConnection).to.exist;
expect(targetConnection.type).to.equal('bpmn:SequenceFlow');
expect(startEvent.outgoing[0]).to.equal(intermediateThrowEvent.incoming[0]);
expect(task.incoming[1]).to.equal(intermediateThrowEvent.outgoing[0]);
// split target at insertion point
expect(sequenceFlow).to.have.waypoints(flatten([
originalWaypoints.slice(0, 2),
{ x: 340, y: 192 }
]));
expect(sequenceFlow).to.have.endDocking({ x: 340, y: 210 });
expect(targetConnection).to.have.waypoints(flatten([
{ x: 340, y: 228 },
originalWaypoints.slice(2)
]));
expect(targetConnection).to.have.startDocking({ x: 340, y: 210 });
}
));
it('should connect start -> target -> end (hovering parent)', inject(
function(dragging, move, elementRegistry, selection, canvas) {
// given
var intermediateThrowEvent = elementRegistry.get('IntermediateThrowEvent_foo');
var startEvent = elementRegistry.get('StartEvent'),
sequenceFlow = elementRegistry.get('SequenceFlow_1'),
task = elementRegistry.get('Task_1'),
rootElement = canvas.getRootElement(),
rootElementGfx = elementRegistry.getGraphics(rootElement);
var originalWaypoints = sequenceFlow.waypoints;
// when
selection.select(intermediateThrowEvent);
move.start(canvasEvent({ x: 0, y: 0 }), intermediateThrowEvent);
dragging.hover({
element: rootElement,
gfx: rootElementGfx
});
dragging.move(canvasEvent({ x: 149, y: 0 }));
dragging.end();
// then
var targetConnection = intermediateThrowEvent.outgoing[0];
// new incoming connection
expect(intermediateThrowEvent.incoming).to.have.length(1);
expect(intermediateThrowEvent.incoming[0]).to.eql(sequenceFlow);
// new outgoing connection
expect(intermediateThrowEvent.outgoing).to.have.length(1);
expect(targetConnection).to.exist;
expect(targetConnection.type).to.equal('bpmn:SequenceFlow');
expect(startEvent.outgoing[0]).to.equal(intermediateThrowEvent.incoming[0]);
expect(task.incoming[1]).to.equal(intermediateThrowEvent.outgoing[0]);
// split target at insertion point
expect(sequenceFlow).to.have.waypoints(flatten([
originalWaypoints.slice(0, 2),
{ x: 340, y: 192 }
]));
expect(sequenceFlow).to.have.endDocking({ x: 340, y: 210 });
expect(targetConnection).to.have.waypoints(flatten([
{ x: 340, y: 228 },
originalWaypoints.slice(2)
]));
expect(targetConnection).to.have.startDocking({ x: 340, y: 210 });
}
));
it('should connect start -> target -> end (with bendpointBefore inside bbox)', inject(
function(elementRegistry, selection, move, dragging) {
// given
var task3 = elementRegistry.get('Task_3'),
sequenceFlow = elementRegistry.get('SequenceFlow_1'),
sequenceFlowGfx = elementRegistry.getGraphics(sequenceFlow),
originalWaypoints = sequenceFlow.waypoints;
// when
selection.select(task3);
move.start(canvasEvent({ x: 0, y: 0 }), task3);
dragging.hover({
element: sequenceFlow,
gfx: sequenceFlowGfx
});
dragging.move(canvasEvent({ x: 149, y: -130 }));
dragging.end();
// then
// split target but don't keep insertion point
expect(sequenceFlow).to.have.waypoints(flatten([
originalWaypoints.slice(0, 2),
{ x: 340, y: 241 }
]));
expect(sequenceFlow).to.have.endDocking({ x: 340, y: 281 });
}
));
it('should connect start -> target -> end (with bendpointAfter inside bbox)', inject(
function(elementRegistry, selection, move, dragging) {
// given
var task3 = elementRegistry.get('Task_3'),
sequenceFlow = elementRegistry.get('SequenceFlow_1'),
sequenceFlowGfx = elementRegistry.getGraphics(sequenceFlow),
originalWaypoints = sequenceFlow.waypoints;
// when
selection.select(task3);
move.start(canvasEvent({ x: 0, y: 0 }), task3);
dragging.hover({
element: sequenceFlow,
gfx: sequenceFlowGfx
});
dragging.move(canvasEvent({ x: 170, y: -110 }));
dragging.end();
// then
// split target but don't keep insertion point
expect(sequenceFlow).to.have.waypoints(flatten([
originalWaypoints.slice(0, 2),
{ x: 340, y: 261 }
]));
expect(sequenceFlow).to.have.endDocking({ x: 340, y: 299 });
}
));
it('should connect start -> target -> end (keeping target outgoing flows)', inject(
function(elementRegistry, selection, move, dragging) {
// given
var gateway_C = elementRegistry.get('Gateway_C'),
task_B = elementRegistry.get('task_B'),
sequenceFlow = elementRegistry.get('SequenceFlow_D'),
sequenceFlowGfx = elementRegistry.getGraphics(sequenceFlow);
// when
selection.select(gateway_C);
move.start(canvasEvent({ x: 0, y: 0 }), gateway_C);
dragging.hover({
element: sequenceFlow,
gfx: sequenceFlowGfx
});
dragging.move(canvasEvent({ x: 160, y: -130 }));
dragging.end();
// then
expect(gateway_C.outgoing).to.have.length(2);
expect(gateway_C.outgoing[0].gateway_C).to.eql(task_B);
}
));
it('should connect start -> target', inject(
function(modeling, elementRegistry, selection, move, dragging) {
// given
var endEventShape = elementRegistry.get('EndEvent_foo');
var sequenceFlow = elementRegistry.get('SequenceFlow_1'),
sequenceFlowGfx = elementRegistry.getGraphics(sequenceFlow),
originalWaypoints = sequenceFlow.waypoints;
// when
selection.select(endEventShape);
move.start(canvasEvent({ x: 0, y: 0 }), endEventShape);
dragging.hover({
element: sequenceFlow,
gfx: sequenceFlowGfx
});
dragging.move(canvasEvent({ x: 150, y: 0 }));
dragging.end();
// then
// new incoming connection
expect(endEventShape.incoming).to.have.length(1);
expect(endEventShape.incoming[0]).to.eql(sequenceFlow);
// no outgoing edges
expect(endEventShape.outgoing).to.have.length(0);
// split target at insertion point
expect(sequenceFlow).to.have.waypoints(flatten([
originalWaypoints.slice(0, 2),
{ x: 340, y: 281 }
]));
}
));
it('should connect target -> end', inject(
function(modeling, elementRegistry, dragging, selection, move) {
var startEventShape = elementRegistry.get('StartEvent_foo');
var sequenceFlow = elementRegistry.get('SequenceFlow_1'),
sequenceFlowGfx = elementRegistry.getGraphics(sequenceFlow),
originalWaypoints = sequenceFlow.waypoints;
// when
selection.select(startEventShape);
move.start(canvasEvent({ x: 0, y: 0 }), startEventShape);
dragging.hover({
element: sequenceFlow,
gfx: sequenceFlowGfx
});
dragging.move(canvasEvent({ x: -215, y: 0 }));
dragging.end();
// then
// no incoming connection
expect(startEventShape.incoming).to.have.length(0);
// 1 outgoing connection
expect(startEventShape.outgoing).to.have.length(1);
expect(startEventShape.outgoing[0]).to.eql(sequenceFlow);
// split target at insertion point
expect(sequenceFlow).to.have.waypoints(flatten([
{ x: 338, y: 228 },
originalWaypoints.slice(2)
]));
}
));
it('should undo', inject(
function(modeling, elementRegistry, dragging, selection, move, commandStack) {
// given
var startEventShape = elementRegistry.get('StartEvent_foo');
var sequenceFlow = elementRegistry.get('SequenceFlow_1'),
sequenceFlowGfx = elementRegistry.getGraphics(sequenceFlow),
originalWaypoints = sequenceFlow.waypoints;
selection.select(startEventShape);
move.start(canvasEvent({ x: 0, y: 0 }), startEventShape);
dragging.hover({
element: sequenceFlow,
gfx: sequenceFlowGfx
});
dragging.move(canvasEvent({ x: -215, y: 0 }));
dragging.end();
// when
commandStack.undo();
// then
// no incoming connection
expect(startEventShape.incoming).to.have.length(0);
// no outgoing edges
expect(startEventShape.outgoing).to.have.length(0);
// split target at insertion point
expect(sequenceFlow).to.have.waypoints(flatten([ originalWaypoints ]));
}
));
it('should not insert on inaccuratly found intersection', inject(
function(dragging, move, elementRegistry, selection) {
// given
var intermediateThrowEvent = elementRegistry.get('IntermediateThrowEvent_foo');
var sequenceFlow = elementRegistry.get('SequenceFlow_1'),
sequenceFlowGfx = elementRegistry.getGraphics(sequenceFlow);
// when
selection.select(intermediateThrowEvent);
move.start(canvasEvent({ x: 0, y: 0 }), intermediateThrowEvent);
dragging.hover({
element: sequenceFlow,
gfx: sequenceFlowGfx
});
dragging.move(canvasEvent({ x: 20, y: -90 }));
dragging.end();
// then
expect(intermediateThrowEvent.incoming).to.have.lengthOf(0);
expect(intermediateThrowEvent.outgoing).to.have.lengthOf(0);
}
));
it('should remove redundant flows', inject(
function(elementRegistry, selection, move, dragging) {
var existingIncoming = elementRegistry.get('SequenceFlow_3'),
existingOutgoing = elementRegistry.get('SequenceFlow_4');
// given
var element = elementRegistry.get('Task_4');
var targetFlow = elementRegistry.get('SequenceFlow_1'),
targetFlowGfx = elementRegistry.getGraphics(targetFlow);
// when
selection.select(element);
move.start(canvasEvent({ x: 0, y: 0 }), element);
dragging.hover({
element: targetFlow,
gfx: targetFlowGfx
});
dragging.move(canvasEvent({ x: -40, y: 179 }));
dragging.end();
// then
// existing connections are removed, as they are duplicates
expect(element.incoming).not.to.contain(existingIncoming);
expect(element.outgoing).not.to.contain(existingOutgoing);
}
));
});
});
describe('rules', function() {
it('should be allowed for an IntermediateThrowEvent', inject(
function(elementRegistry, bpmnRules, elementFactory) {
// when
var sequenceFlow = elementRegistry.get('SequenceFlow_1');
var intermediateThrowEvent = elementFactory.createShape({
type: 'bpmn:IntermediateThrowEvent'
});
// then
expect(bpmnRules.canCreate(intermediateThrowEvent, sequenceFlow)).to.be.true;
}
));
it('should not insert participant', inject(
function(rules, elementRegistry, elementFactory) {
// given
var participantShape = elementFactory.createShape({
type: 'bpmn:Participant'
});
var sequenceFlow = elementRegistry.get('SequenceFlow_1');
var dropPosition = { x: 340, y: 120 }; // first bendpoint
// when
var canDrop = rules.allowed('shape.create', {
shape: participantShape,
parent: sequenceFlow,
dropPosition: dropPosition
});
// then
expect(canDrop).to.be.false;
}
));
it('should not insert multiple with "move"', inject(
function(elementRegistry, selection, move, dragging) {
// given
var intermediateThrowEvent = elementRegistry.get('IntermediateThrowEvent_foo'),
endEventShape = elementRegistry.get('EndEvent_foo');
var sequenceFlow = elementRegistry.get('SequenceFlow_1'),
sequenceFlowGfx = elementRegistry.getGraphics(sequenceFlow);
var intInitPosition = {
x: intermediateThrowEvent.x,
y: intermediateThrowEvent.y
},
endInitPosition = {
x: endEventShape.x,
y: endEventShape.y
};
selection.select([ intermediateThrowEvent, endEventShape ]);
// when
move.start(canvasEvent({ x: 0, y: 0 }), intermediateThrowEvent);
dragging.hover({
element: sequenceFlow,
gfx: sequenceFlowGfx
});
dragging.move(canvasEvent({ x: -215, y: 0 }));
dragging.end();
// then
expect(intermediateThrowEvent).to.have.position(intInitPosition);
expect(endEventShape).to.have.position(endInitPosition);
}
));
it('should not insert on sequence flow label', inject(
function(bpmnRules, elementRegistry) {
// given
var eventShape = elementRegistry.get('IntermediateThrowEvent_foo'),
sequenceFlowLabel = elementRegistry.get('SequenceFlow_2').label;
var dropPosition = { x: 675, y: 275 }; // sequence flow label
// when
var canInsert = bpmnRules.canInsert(eventShape, sequenceFlowLabel, dropPosition);
// then
expect(canInsert).to.be.false;
}
));
});
});
================================================
FILE: test/spec/features/modeling/behavior/EventBasedGatewayBehavior.bpmn
================================================
Flow_1
Flow_2
Flow_1
Flow_2
Flow_3
Flow_4
Flow_5
Flow_3
Flow_4
Flow_5
================================================
FILE: test/spec/features/modeling/behavior/EventBasedGatewayBehaviorSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import coreModule from 'lib/core';
import createModule from 'diagram-js/lib/features/create';
import modelingModule from 'lib/features/modeling';
import replaceModule from 'lib/features/replace';
import { createCanvasEvent as canvasEvent } from '../../../../util/MockEvents';
describe('features/modeling/behavior - event-based gateway', function() {
var diagramXML = require('./EventBasedGatewayBehavior.bpmn');
var testModules = [
coreModule,
modelingModule,
replaceModule
];
describe('create connection', function() {
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
it('event-based gateway to receive task', inject(
function(elementRegistry, modeling) {
// given
var eventBasedGateway = elementRegistry.get('EventBasedGateway_1'),
receiveTask = elementRegistry.get('ReceiveTask_1');
// assume
expect(receiveTask.incoming).to.have.length(1);
// when
var connection = modeling.connect(eventBasedGateway, receiveTask, {
type: 'bpmn:SequenceFlow'
});
// then
expect(receiveTask.incoming).to.have.length(1);
expect(receiveTask.incoming[ 0 ]).to.equal(connection);
}
));
it('event-based gateway to receive task (duplicate connection)', inject(
function(elementRegistry, modeling) {
// given
var eventBasedGateway = elementRegistry.get('EventBasedGateway_1'),
receiveTask = elementRegistry.get('ReceiveTask_2');
// assume
expect(receiveTask.incoming).to.have.length(1);
// when
var connection = modeling.connect(eventBasedGateway, receiveTask, {
type: 'bpmn:SequenceFlow'
});
// then
expect(receiveTask.incoming).to.have.length(1);
expect(receiveTask.incoming[ 0 ]).to.equal(connection);
}
));
it('exclusive gateway to receive task', inject(
function(elementRegistry, modeling) {
// given
var eventBasedGateway = elementRegistry.get('ExclusiveGateway_1'),
receiveTask = elementRegistry.get('ReceiveTask_2');
// assume
expect(receiveTask.incoming).to.have.length(1);
// when
var connection = modeling.connect(eventBasedGateway, receiveTask, {
type: 'bpmn:SequenceFlow'
});
// then
expect(receiveTask.incoming).to.have.length(1);
expect(receiveTask.incoming[ 0 ]).to.equal(connection);
}
));
});
describe('replace shape', function() {
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
it('exclusive gateway with event-based gateway', inject(
function(bpmnReplace, elementRegistry) {
// given
var exclusiveGateway = elementRegistry.get('ExclusiveGateway_3'),
receiveTask = elementRegistry.get('ReceiveTask_3');
// assume
expect(exclusiveGateway.outgoing).to.have.length(2);
expect(receiveTask.incoming).to.have.length(3);
// when
var eventBasedGateway = bpmnReplace.replaceElement(exclusiveGateway, {
type: 'bpmn:EventBasedGateway'
});
// then
expect(eventBasedGateway.outgoing).to.have.length(1);
expect(receiveTask.incoming).to.have.length(1);
}
));
it('event-based gateway with exclusive gateway', inject(
function(bpmnReplace, elementRegistry) {
// given
var eventBasedGateway = elementRegistry.get('EventBasedGateway_1');
// assume
expect(eventBasedGateway.outgoing).to.have.length(1);
// when
var exclusiveGateway = bpmnReplace.replaceElement(eventBasedGateway, {
type: 'bpmn:ExclusiveGateway'
});
// then
expect(exclusiveGateway.outgoing).to.have.length(1);
}
));
});
});
describe('features/modeling/behavior - event-based gateway - integration', function() {
var diagramXML = require('./EventBasedGatewayBehavior.bpmn');
var testModules = [
coreModule,
createModule,
modelingModule,
replaceModule
];
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
it('should create with target', inject(
function(create, elementRegistry, elementFactory, dragging) {
// given
const gateway = elementFactory.createShape({
type: 'bpmn:EventBasedGateway',
x: 0,
y: 0
});
const event = elementFactory.createShape({
type: 'bpmn:IntermediateCatchEvent',
eventDefinitionType: 'bpmn:MessageEventDefinition',
x: 120,
y: 120
});
const waypoints = [
{ x: gateway.x, y: gateway.y },
{ x: event.x, y: event.y }
];
const connection = elementFactory.createConnection({
type: 'bpmn:SequenceFlow',
source: gateway,
target: event,
waypoints
});
const rootElement = elementRegistry.get('Process_1');
const rootGfx = elementRegistry.getGraphics('Process_1');
// when
create.start(canvasEvent({ x: 0, y: 0 }), [ gateway, event, connection ]);
dragging.hover({ element: rootElement, gfx: rootGfx });
dragging.move(canvasEvent({ x: 100, y: 200 }));
dragging.end();
// then
expect(elementRegistry.get(connection.id)).to.exist;
}
));
});
================================================
FILE: test/spec/features/modeling/behavior/FixHoverBehavior.annotation.bpmn
================================================
================================================
FILE: test/spec/features/modeling/behavior/FixHoverBehavior.group.bpmn
================================================
================================================
FILE: test/spec/features/modeling/behavior/FixHoverBehavior.label.bpmn
================================================
================================================
FILE: test/spec/features/modeling/behavior/FixHoverBehavior.lane-connect.bpmn
================================================
================================================
FILE: test/spec/features/modeling/behavior/FixHoverBehavior.participant.bpmn
================================================
Task_1
================================================
FILE: test/spec/features/modeling/behavior/FixHoverBehaviorSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import coreModule from 'lib/core';
import createModule from 'diagram-js/lib/features/create';
import modelingModule from 'lib/features/modeling';
import moveModule from 'diagram-js/lib/features/move';
import globalConnectModule from 'diagram-js/lib/features/global-connect';
import connectionPreview from 'diagram-js/lib/features/connection-preview';
import bendpointsModule from 'diagram-js/lib/features/bendpoints';
import { createCanvasEvent as canvasEvent } from '../../../../util/MockEvents';
var testModules = [
coreModule,
createModule,
moveModule,
modelingModule
];
describe('features/modeling/behavior - fix hover', function() {
describe('drop on lane', function() {
var diagramXML = require('./FixHoverBehavior.participant.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
beforeEach(inject(function(dragging) {
dragging.setOptions({ manual: true });
}));
var lane,
laneGfx,
participant,
participantGfx;
beforeEach(inject(function(elementRegistry) {
participant = elementRegistry.get('Participant_1');
participantGfx = elementRegistry.getGraphics(participant);
lane = elementRegistry.get('Lane_1');
laneGfx = elementRegistry.getGraphics(lane);
}));
describe('create', function() {
it('should participant', inject(
function(create, dragging, elementFactory) {
// given
var task = elementFactory.createShape({ type: 'bpmn:Task' });
create.start(canvasEvent({ x: 0, y: 0 }), task, true);
// when
dragging.hover({ element: lane, gfx: laneGfx });
dragging.move(canvasEvent({ x: 200, y: 200 }));
dragging.end();
// then
expect(task.parent).to.equal(participant);
}
));
it('should participant', inject(
function(create, dragging, elementFactory, eventBus) {
// given
var task = elementFactory.createShape({ type: 'bpmn:Task' });
var outSpy = sinon.spy(function(event) {
expect(event.hover).to.eql(participant);
expect(event.hoverGfx).to.eql(participantGfx);
});
eventBus.on('create.out', outSpy);
create.start(canvasEvent({ x: 0, y: 0 }), task, true);
dragging.hover({ element: lane, gfx: laneGfx });
dragging.move(canvasEvent({ x: 200, y: 200 }));
// when
dragging.out({ element: lane, gfx: laneGfx });
dragging.end();
// then
expect(outSpy).to.have.been.calledOnce;
}
));
});
describe('move', function() {
it('should participant', inject(
function(dragging, elementRegistry, move) {
// given
var task = elementRegistry.get('Task_1');
move.start(canvasEvent({ x: 440, y: 220 }), task, true);
// when
dragging.hover({ element: lane, gfx: laneGfx });
dragging.move(canvasEvent({ x: 240, y: 220 }));
dragging.end();
// then
expect(task.parent).to.equal(participant);
}
));
it('should participant', inject(
function(dragging, elementRegistry, move, eventBus) {
// given
var task = elementRegistry.get('Task_1');
var outSpy = sinon.spy(function(event) {
expect(event.hover).to.eql(participant);
expect(event.hoverGfx).to.eql(participantGfx);
});
eventBus.on('shape.move.out', outSpy);
move.start(canvasEvent({ x: 440, y: 220 }), task, true);
dragging.hover({ element: lane, gfx: laneGfx });
dragging.move(canvasEvent({ x: 240, y: 220 }));
// when
dragging.out({ element: lane, gfx: laneGfx });
// then
expect(outSpy).to.have.been.calledOnce;
}
));
});
});
describe('label', function() {
var diagramXML = require('./FixHoverBehavior.label.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
beforeEach(inject(function(dragging) {
dragging.setOptions({ manual: true });
}));
describe('move', function() {
it('should root', inject(
function(dragging, elementRegistry, move, canvas) {
// given
var startEvent = elementRegistry.get('StartEvent');
var label = startEvent.label;
move.start(canvasEvent({ x: 175, y: 150 }), label, true);
// when
dragging.hover({ element: startEvent, gfx: elementRegistry.getGraphics(startEvent) });
dragging.move(canvasEvent({ x: 240, y: 220 }));
dragging.end();
// then
expect(label.parent).to.equal(canvas.getRootElement());
}
));
});
});
describe('group', function() {
var diagramXML = require('./FixHoverBehavior.group.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
beforeEach(inject(function(dragging) {
dragging.setOptions({ manual: true });
}));
describe('create', function() {
it('should root', inject(
function(dragging, elementFactory, elementRegistry, create, canvas) {
// given
var task = elementRegistry.get('Task');
var group = elementFactory.createShape({ type: 'bpmn:Group' });
create.start(canvasEvent({ x: 0, y: 0 }), group, true);
// when
dragging.hover({ element: task, gfx: elementRegistry.getGraphics(task) });
dragging.move(canvasEvent({ x: 240, y: 220 }));
dragging.end();
// then
expect(group.parent).to.equal(canvas.getRootElement());
}
));
});
describe('move', function() {
it('should root', inject(
function(dragging, elementRegistry, move, canvas) {
// given
var task = elementRegistry.get('Task');
var group = elementRegistry.get('Group');
move.start(canvasEvent({ x: 175, y: 150 }), group, true);
// when
dragging.hover({ element: task, gfx: elementRegistry.getGraphics(task) });
dragging.move(canvasEvent({ x: 240, y: 220 }));
dragging.end();
// then
expect(group.parent).to.equal(canvas.getRootElement());
}
));
});
});
describe('Annotation', function() {
var diagramXML = require('./FixHoverBehavior.annotation.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
beforeEach(inject(function(dragging) {
dragging.setOptions({ manual: true });
}));
describe('create', function() {
it('should root', inject(
function(dragging, elementFactory, elementRegistry, create, canvas) {
// given
var task = elementRegistry.get('Task');
var annotation = elementFactory.createShape({ type: 'bpmn:TextAnnotation' });
create.start(canvasEvent({ x: 0, y: 0 }), annotation, true);
// when
dragging.hover({ element: task, gfx: elementRegistry.getGraphics(task) });
dragging.move(canvasEvent({ x: 240, y: 220 }));
dragging.end();
// then
expect(annotation.parent).to.equal(canvas.getRootElement());
}
));
});
describe('move', function() {
it('should root', inject(
function(dragging, elementRegistry, move, canvas) {
// given
var task = elementRegistry.get('Task');
var annotation = elementRegistry.get('TextAnnotation_1');
move.start(canvasEvent({ x: 175, y: 150 }), annotation, true);
// when
dragging.hover({ element: task, gfx: elementRegistry.getGraphics(task) });
dragging.move(canvasEvent({ x: 240, y: 220 }));
dragging.end();
// then
expect(annotation.parent).to.equal(canvas.getRootElement());
}
));
});
});
describe('connect lane', function() {
var diagramXML = require('./FixHoverBehavior.lane-connect.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules.concat([
globalConnectModule,
bendpointsModule,
connectionPreview
])
}));
beforeEach(inject(function(dragging) {
dragging.setOptions({ manual: true });
}));
describe('global-connect', function() {
it('should set global connect source to participant', inject(
function(globalConnect, elementRegistry, eventBus, dragging) {
// given
var participant_lanes = elementRegistry.get('Participant_Lanes');
var lane_1 = elementRegistry.get('Lane_1');
var connectSpy = sinon.spy(function(event) {
expect(event.context.startTarget).to.eql(participant_lanes);
});
eventBus.once('global-connect.end', connectSpy);
// when
globalConnect.start(canvasEvent({ x: 0, y: 0 }));
dragging.move(canvasEvent({ x: 150, y: 130 }));
dragging.hover(canvasEvent({ x: 150, y: 130 }, { element: lane_1 }));
dragging.end(canvasEvent({ x: 0, y: 0 }));
// then
expect(connectSpy).to.have.been.called;
}
));
describe('fix hover', function() {
it('on out', inject(
function(globalConnect, dragging, elementRegistry, eventBus) {
// given
var participant_lanes = elementRegistry.get('Participant_Lanes');
var lane_1 = elementRegistry.get('Lane_1');
var connectSpy = sinon.spy(function(event) {
expect(event.hover).to.eql(participant_lanes);
});
// when
globalConnect.start(canvasEvent({ x: 240, y: 0 }));
dragging.move(canvasEvent({ x: 240, y: 300 }));
dragging.hover(canvasEvent({ x: 240, y: 300 }, { element: lane_1 }));
eventBus.once('global-connect.out', connectSpy);
dragging.out();
// then
expect(connectSpy).to.have.been.calledOnce;
}
));
it('on end/cleanup', inject(
function(globalConnect, dragging, elementRegistry, eventBus) {
// given
var participant_lanes = elementRegistry.get('Participant_Lanes');
var lane_1 = elementRegistry.get('Lane_1');
var connectSpy = sinon.spy(function(event) {
expect(event.hover).to.eql(participant_lanes);
});
eventBus.on('global-connect.end', connectSpy);
eventBus.on('global-connect.cleanup', connectSpy);
// when
globalConnect.start(canvasEvent({ x: 240, y: 0 }));
dragging.move(canvasEvent({ x: 240, y: 300 }));
dragging.hover(canvasEvent({ x: 240, y: 300 }, { element: lane_1 }));
dragging.end();
// then
expect(connectSpy).to.have.been.calledTwice;
}
));
});
});
describe('reconnect', function() {
it('should set hover to participant', inject(
function(bendpointMove, elementRegistry, eventBus, dragging) {
// given
var participant_lanes = elementRegistry.get('Participant_Lanes');
var lane_1 = elementRegistry.get('Lane_1');
var messageFlow = elementRegistry.get('MessageFlow_2');
var connectSpy = sinon.spy(function(event) {
expect(event.context.hover).to.equal(participant_lanes);
});
eventBus.once('bendpoint.move.end', connectSpy);
// when
bendpointMove.start(canvasEvent({ x: 240, y: 200 }), messageFlow, 0);
dragging.move(canvasEvent({ x: 240, y: 280 }));
dragging.hover({ element: lane_1, gfx: elementRegistry.getGraphics(lane_1) });
dragging.end();
// then
expect(connectSpy).to.have.been.called;
}
));
it('should set end to participant', inject(
function(bendpointMove, elementRegistry, eventBus, dragging) {
// given
var participant_lanes = elementRegistry.get('Participant_Lanes');
var lane_1 = elementRegistry.get('Lane_1');
var messageFlow = elementRegistry.get('MessageFlow_1');
var connectSpy = sinon.spy(function(event) {
expect(event.context.target).to.eql(participant_lanes);
});
eventBus.once('bendpoint.move.end', connectSpy);
// when
bendpointMove.start(canvasEvent({ x: 240, y: 200 }), messageFlow, 1);
dragging.move(canvasEvent({ x: 240, y: 280 }));
dragging.hover({ element: lane_1, gfx: elementRegistry.getGraphics(lane_1) });
dragging.end();
// then
expect(connectSpy).to.have.been.called;
}
));
});
describe('connect', function() {
it('should set start to participant', inject(
function(connect, dragging, elementRegistry, eventBus) {
// given
var participant_lanes = elementRegistry.get('Participant_Lanes');
var participant_no_lanes = elementRegistry.get('Participant_No_Lanes');
var lane_1 = elementRegistry.get('Lane_1');
var connectSpy = sinon.spy(function(event) {
expect(event.context.source).to.eql(participant_lanes);
});
eventBus.once('connect.end', connectSpy);
// when
connect.start(canvasEvent({ x: 240, y: 300 }), lane_1);
dragging.move(canvasEvent({ x: 240, y: 0 }));
dragging.hover(canvasEvent({ x: 240, y: 0 }, { element: participant_no_lanes }));
dragging.end();
// then
expect(connectSpy).to.have.been.called;
}
));
it('should set end to participant', inject(
function(connect, dragging, elementRegistry, eventBus) {
// given
var participant_lanes = elementRegistry.get('Participant_Lanes');
var participant_no_lanes = elementRegistry.get('Participant_No_Lanes');
var lane_1 = elementRegistry.get('Lane_1');
var connectSpy = sinon.spy(function(event) {
var context = event.context,
target = context.target;
expect(target).to.eql(participant_lanes);
});
eventBus.once('connect.end', connectSpy);
// when
connect.start(canvasEvent({ x: 240, y: 0 }), participant_no_lanes);
dragging.move(canvasEvent({ x: 240, y: 300 }));
dragging.hover(canvasEvent({ x: 240, y: 300 }, { element: lane_1 }));
dragging.end();
// then
expect(connectSpy).to.have.been.calledOnce;
}
));
describe('fix hover', function() {
it('on out', inject(
function(connect, dragging, elementRegistry, eventBus) {
// given
var participant_lanes = elementRegistry.get('Participant_Lanes');
var participant_no_lanes = elementRegistry.get('Participant_No_Lanes');
var lane_1 = elementRegistry.get('Lane_1');
var connectSpy = sinon.spy(function(event) {
expect(event.hover).to.eql(participant_lanes);
});
// when
connect.start(canvasEvent({ x: 240, y: 0 }), participant_no_lanes);
dragging.move(canvasEvent({ x: 240, y: 300 }));
dragging.hover(canvasEvent({ x: 240, y: 300 }, { element: lane_1 }));
eventBus.once('connect.out', connectSpy);
dragging.out();
// then
expect(connectSpy).to.have.been.calledOnce;
}
));
it('on end/cleanup', inject(
function(connect, dragging, elementRegistry, eventBus) {
// given
var participant_lanes = elementRegistry.get('Participant_Lanes');
var participant_no_lanes = elementRegistry.get('Participant_No_Lanes');
var lane_1 = elementRegistry.get('Lane_1');
var connectSpy = sinon.spy(function(event) {
expect(event.hover).to.eql(participant_lanes);
});
eventBus.on('connect.end', connectSpy);
eventBus.on('connect.cleanup', connectSpy);
// when
connect.start(canvasEvent({ x: 240, y: 0 }), participant_no_lanes);
dragging.move(canvasEvent({ x: 240, y: 300 }));
dragging.hover(canvasEvent({ x: 240, y: 300 }, { element: lane_1 }));
dragging.end();
// then
expect(connectSpy).to.have.been.calledTwice;
}
));
});
});
});
describe('participant with lane', function() {
var diagramXML = require('./FixHoverBehavior.lane-connect.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules.concat([
globalConnectModule,
bendpointsModule
])
}));
beforeEach(inject(function(dragging) {
dragging.setOptions({ manual: true });
}));
it('should move the participant when lane is dragged', inject(
function(canvas, eventBus, elementRegistry, move, dragging) {
// given
var lane = elementRegistry.get('Lane_1'),
participant = elementRegistry.get('Participant_Lanes');
var rootElement = canvas.getRootElement(),
rootElementGfx = canvas.getGraphics(rootElement);
var moveEndSpy = sinon.spy(function(event) {
expect(event.context.shape).to.equal(participant);
});
eventBus.on('shape.move.end', moveEndSpy);
// when
move.start(canvasEvent({ x: 100, y: 100 }), lane);
dragging.move(canvasEvent({ x: 140, y: 120 }));
dragging.hover({
element: rootElement,
gfx: rootElementGfx
});
dragging.end();
// then
expect(moveEndSpy).to.have.been.calledOnce;
}
));
});
describe('space tool', function() {
var diagramXML = require('./FixHoverBehavior.participant.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
beforeEach(inject(function(dragging) {
dragging.setOptions({ manual: true });
}));
it('should participant', inject(
function(dragging, elementRegistry, spaceTool) {
// given
var lane = elementRegistry.get('Lane_1'),
participant = elementRegistry.get('Participant_1');
spaceTool.activateMakeSpace(canvasEvent({ x: 150, y: 0 }));
expect(participant.width).to.equal(600);
// when
dragging.hover({ element: lane });
dragging.move(canvasEvent({ x: 250, y: 0 }, {
button: 0,
shiftKey: true
}));
dragging.end();
// then
expect(participant.width).to.equal(700);
}
));
});
});
================================================
FILE: test/spec/features/modeling/behavior/GroupBehaviorSpec.bpmn
================================================
================================================
FILE: test/spec/features/modeling/behavior/GroupBehaviorSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import {
getBusinessObject,
is
} from 'lib/util/ModelUtil';
import bpmnCopyPasteModule from 'lib/features/copy-paste';
import copyPasteModule from 'diagram-js/lib/features/copy-paste';
import modelingModule from 'lib/features/modeling';
import coreModule from 'lib/core';
import { find } from 'min-dash';
describe('features/modeling/behavior - groups', function() {
var testModules = [
coreModule,
copyPasteModule,
bpmnCopyPasteModule,
modelingModule
];
var processDiagramXML = require('./GroupBehaviorSpec.bpmn');
beforeEach(bootstrapModeler(processDiagramXML, {
modules: testModules.concat(modelingModule)
}));
describe('creation', function() {
it('should NOT create new CategoryValue if one exists', inject(
function(canvas, elementFactory, elementRegistry, modeling) {
// given
var group1 = elementRegistry.get('Group_1'),
categoryValue = getBusinessObject(group1).categoryValueRef,
root = canvas.getRootElement(),
definitions = getBusinessObject(root).$parent,
originalSize = definitions.get('rootElements').length;
var group = elementFactory.createShape({ type: 'bpmn:Group' });
getBusinessObject(group).categoryValueRef = categoryValue;
// when
var groupShape = modeling.createShape(group, { x: 100, y: 100 }, root),
categoryValueRef = getBusinessObject(groupShape).categoryValueRef;
// then
expect(categoryValueRef).to.eql(categoryValue);
expect(definitions.get('rootElements')).to.have.length(originalSize);
}
));
describe('should NOT create Category for new Group', function() {
it('execute', inject(function(canvas, elementFactory, modeling) {
// given
var root = canvas.getRootElement();
// when
var group = modeling.createShape({ type: 'bpmn:Group' }, { x: 100, y: 100 }, root),
groupBo = getBusinessObject(group),
categoryValue = groupBo.categoryValueRef;
// then
expect(categoryValue).not.to.exist;
}));
it('undo', inject(function(canvas, elementFactory, modeling, commandStack) {
// given
var root = canvas.getRootElement();
// when
var group = modeling.createShape({ type: 'bpmn:Group' }, { x: 100, y: 100 }, root),
groupBo = getBusinessObject(group);
commandStack.undo();
var categoryValue = groupBo.categoryValueRef;
// then
expect(categoryValue).not.to.exist;
}));
it('redo', inject(function(canvas, elementFactory, modeling, commandStack) {
// given
var root = canvas.getRootElement();
// when
var group = modeling.createShape({ type: 'bpmn:Group' }, { x: 100, y: 100 }, root),
groupBo = getBusinessObject(group);
commandStack.undo();
commandStack.redo();
// then
expect(groupBo.categoryValueRef).not.to.exist;
}));
});
describe('should paste with Category', function() {
var groupBo, rootElements;
beforeEach(inject(function(canvas, copyPaste, elementRegistry) {
// given
var group = elementRegistry.get('Group_1'),
rootElement = canvas.getRootElement();
copyPaste.copy(group);
// when
var elements = copyPaste.paste({
element: rootElement,
point: {
x: 500,
y: 500
}
});
group = find(elements, function(element) {
return is(element, 'bpmn:Group');
});
groupBo = getBusinessObject(group);
rootElements = getBusinessObject(canvas.getRootElement()).$parent.rootElements;
}));
it('', function() {
// then
expect(groupBo.categoryValueRef).to.exist;
expect(groupBo.categoryValueRef.$parent).to.exist;
expect(groupBo.categoryValueRef.value).to.equal('Value 1');
expect(rootElements).to.have.length(4);
});
it('', inject(function(commandStack) {
// when
commandStack.undo();
// then
expect(rootElements).to.have.length(3);
}));
it('', inject(function(commandStack) {
// given
var categoryValue = groupBo.categoryValueRef,
category = categoryValue.$parent;
// when
commandStack.undo();
commandStack.redo();
// then
expect(groupBo.categoryValueRef).to.equal(categoryValue);
expect(groupBo.categoryValueRef.$parent).to.equal(category);
expect(rootElements).to.have.length(4);
}));
});
it('should create new Category in definitions', inject(
function(canvas, elementFactory, modeling, bpmnjs) {
// given
var root = canvas.findRoot('Subprocess_1_plane'),
definitions = bpmnjs.getDefinitions();
// operate on sub-process plane
canvas.setRootElement(root);
// when
var group = modeling.createShape({ type: 'bpmn:Group' }, { x: 100, y: 100 }, root),
groupBo = getBusinessObject(group);
// create label
modeling.updateLabel(group, 'FOO BAR');
var categoryValue = groupBo.categoryValueRef,
category = categoryValue.$parent;
// then
expect(categoryValue).to.exist;
expect(category).to.exist;
expect(category.get('categoryValue')).to.include(categoryValue);
expect(definitions.get('rootElements')).to.include(category);
}
));
});
describe('deletion', function() {
it('should NOT remove CategoryValue if still referenced', inject(
function(elementRegistry, modeling) {
// given
var groupShape = elementRegistry.get('Group_1'),
groupBo = getBusinessObject(groupShape);
var categoryValue = groupBo.categoryValueRef,
category = categoryValue.$parent;
// when
modeling.removeShape(groupShape);
// then
expect(groupBo.categoryValueRef).not.to.exist;
expect(category.get('categoryValue')).to.contain(categoryValue);
}
));
it('should NOT remove Category if still referenced', inject(
function(canvas, elementRegistry, modeling) {
// given
var groupShape = elementRegistry.get('Group_3'),
groupBo = getBusinessObject(groupShape),
root = canvas.getRootElement(),
definitions = getBusinessObject(root).$parent;
var categoryValue = groupBo.categoryValueRef,
category = categoryValue.$parent;
// when
modeling.removeShape(groupShape);
// then
expect(groupBo.categoryValueRef).not.to.exist;
expect(definitions.get('rootElements')).to.contain(category);
}
));
describe('should remove Category + CategoryValue on deletion', function() {
it('execute', inject(function(canvas, elementRegistry, modeling) {
// given
var groupShape = elementRegistry.get('Group_4'),
groupBo = getBusinessObject(groupShape),
root = canvas.getRootElement(),
definitions = getBusinessObject(root).$parent;
var categoryValue = groupBo.categoryValueRef,
category = categoryValue.$parent;
// when
modeling.removeShape(groupShape);
// then
expect(category.get('categoryValue')).not.to.contain(categoryValue);
expect(definitions.get('rootElements')).not.to.contain(category);
}));
it('undo', inject(function(canvas, elementRegistry, modeling, commandStack) {
// given
var groupShape = elementRegistry.get('Group_4'),
groupBo = getBusinessObject(groupShape),
root = canvas.getRootElement(),
definitions = getBusinessObject(root).$parent;
var categoryValue = groupBo.categoryValueRef,
category = categoryValue.$parent;
// when
modeling.removeShape(groupShape);
commandStack.undo();
// then
expect(category.get('categoryValue')).to.include(categoryValue);
expect(definitions.get('rootElements')).to.include(category);
}));
it('redo', inject(function(canvas, elementRegistry, modeling, commandStack) {
// given
var groupShape = elementRegistry.get('Group_4'),
groupBo = getBusinessObject(groupShape),
root = canvas.getRootElement(),
definitions = getBusinessObject(root).$parent;
var categoryValue = groupBo.categoryValueRef,
category = categoryValue.$parent;
// when
modeling.removeShape(groupShape);
commandStack.undo();
commandStack.redo();
// then
expect(category.get('categoryValue')).not.to.include(categoryValue);
expect(definitions.get('rootElements')).not.to.include(category);
}));
});
describe('should handle non-existing CategoryValue gracefully', function() {
it('execute', inject(function(elementRegistry, modeling) {
// given
var groupShape = elementRegistry.get('Group_NO_CATEGORY_VALUE'),
groupBo = getBusinessObject(groupShape);
// assume
expect(groupBo.categoryValueRef).not.to.exist;
// then
modeling.removeShape(groupShape);
}));
it('undo', inject(function(elementRegistry, modeling, commandStack) {
// given
var groupShape = elementRegistry.get('Group_NO_CATEGORY_VALUE'),
groupBo = getBusinessObject(groupShape);
// when
modeling.removeShape(groupShape);
commandStack.undo();
// then
expect(groupBo.categoryValueRef).not.to.exist;
}));
it('redo', inject(function(elementRegistry, modeling, commandStack) {
// given
var groupShape = elementRegistry.get('Group_NO_CATEGORY_VALUE'),
groupBo = getBusinessObject(groupShape);
// when
modeling.removeShape(groupShape);
commandStack.undo();
commandStack.redo();
// then
expect(groupBo.categoryValueRef).not.to.exist;
}));
});
});
describe('label editing', function() {
describe('should create Category before setting label', function() {
it('execute', inject(function(elementRegistry, modeling) {
// given
var group = elementRegistry.get('Group_NO_CATEGORY_VALUE'),
groupBo = getBusinessObject(group);
// assume
expect(groupBo.categoryValueRef).not.to.exist;
// when
modeling.updateLabel(group, 'Foo bar');
// then
expect(groupBo.categoryValueRef).to.exist;
expect(groupBo.categoryValueRef.value).to.eql('Foo bar');
expect(groupBo.categoryValueRef.$parent).to.exist;
}));
it('undo', inject(function(elementRegistry, modeling, commandStack) {
// given
var group = elementRegistry.get('Group_NO_CATEGORY_VALUE'),
groupBo = getBusinessObject(group);
// assume
expect(groupBo.categoryValueRef).not.to.exist;
// when
modeling.updateLabel(group, 'Foo bar');
commandStack.undo();
// then
expect(groupBo.categoryValueRef).not.to.exist;
}));
it('redo', inject(function(elementRegistry, modeling, commandStack) {
// given
var group = elementRegistry.get('Group_NO_CATEGORY_VALUE'),
groupBo = getBusinessObject(group);
// assume
expect(groupBo.categoryValueRef).not.to.exist;
// when
modeling.updateLabel(group, 'Foo bar');
commandStack.undo();
commandStack.redo();
// then
expect(groupBo.categoryValueRef).to.exist;
expect(groupBo.categoryValueRef.value).to.eql('Foo bar');
expect(groupBo.categoryValueRef.$parent).to.exist;
}));
});
});
});
================================================
FILE: test/spec/features/modeling/behavior/ImportDockingFix.bpmn
================================================
SequenceFlow
SequenceFlow
DataStoreReference
Property_09ddda9
DataStoreReference
Property_0vybrii
================================================
FILE: test/spec/features/modeling/behavior/ImportDockingFixSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import modelingModule from 'lib/features/modeling';
import coreModule from 'lib/core';
describe('features/modeling/behavior - ImportDockingFix', function() {
var diagramXML = require('./ImportDockingFix.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: [
coreModule,
modelingModule
]
}));
it('should correct dockings on import', inject(function(elementRegistry, modeling) {
// when
var sequenceFlowConnection = elementRegistry.get('SequenceFlow'),
associationConnection = elementRegistry.get('DataAssociation_1');
// then
expect(sequenceFlowConnection).to.have.startDocking({ x: 191, y: 120 });
expect(sequenceFlowConnection).to.have.endDocking({ x: 328, y: 120 });
expect(associationConnection).to.have.startDocking({ x: 437, y: 369 });
expect(associationConnection).to.have.endDocking({ x: 328, y: 119 });
}));
});
================================================
FILE: test/spec/features/modeling/behavior/IsHorizontalFix.bpmn
================================================
StartEvent_1
println execution.eventName
println end
Event_01335ir
Event_02alkvt
================================================
FILE: test/spec/features/modeling/behavior/IsHorizontalFixSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import modelingModule from 'lib/features/modeling';
import coreModule from 'lib/core';
import { getDi } from 'lib/util/ModelUtil';
describe('features/modeling/behavior - IsHorizontalFix', function() {
var diagramXML;
describe('set on create', function() {
diagramXML = require('test/fixtures/bpmn/simple.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: [
coreModule,
modelingModule
]
}));
it('should set isHorizontal=true when participant is created',
inject(function(canvas, elementFactory, modeling) {
// given
var processShape = canvas.getRootElement(),
participantShape = elementFactory.createParticipantShape(true);
// when
var participant = modeling.createShape(participantShape, { x: 350, y: 200 }, processShape);
// then
var isHorizontal = getDi(participant).get('isHorizontal');
expect(isHorizontal).to.be.true;
})
);
it('should set isHorizontal=true when lane is created',
inject(function(canvas, elementFactory, modeling) {
// given
var processShape = canvas.getRootElement(),
participantShape = elementFactory.createParticipantShape(true),
participant = modeling.createShape(participantShape, { x: 350, y: 200 }, processShape);
// when
var lane = modeling.addLane(participant, 'bottom');
// then
var isHorizontal = getDi(lane).get('isHorizontal');
expect(isHorizontal).to.be.true;
})
);
});
describe('set on change', function() {
diagramXML = require('./IsHorizontalFix.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: [
coreModule,
modelingModule
]
}));
it('should set isHorizontal=true when participant is moved',
inject(function(elementRegistry, modeling) {
// given
var participant = elementRegistry.get('Participant');
// when
modeling.moveElements([ participant ], { x: 0, y: 0 });
// then
var isHorizontal = getDi(participant).get('isHorizontal');
expect(isHorizontal).to.be.true;
})
);
it('should keep isHorizontal=true when participant is moved',
inject(function(elementRegistry, modeling) {
// given
var participant = elementRegistry.get('Horizontal_Participant');
// when
modeling.moveElements([ participant ], { x: 0, y: 0 });
// then
var isHorizontal = getDi(participant).get('isHorizontal');
expect(isHorizontal).to.be.true;
})
);
it('should keep isHorizontal=false when participant is moved',
inject(function(elementRegistry, modeling) {
// given
var participant = elementRegistry.get('Vertical_Participant');
// when
modeling.moveElements([ participant ], { x: 0, y: 0 });
// then
var isHorizontal = getDi(participant).get('isHorizontal');
expect(isHorizontal).to.be.false;
})
);
it('should set isHorizontal=true when lane is moved',
inject(function(elementRegistry, modeling) {
// given
var lane = elementRegistry.get('Lane');
// when
modeling.moveElements([ lane ], { x: 0, y: 0 });
// then
var isHorizontal = getDi(lane).get('isHorizontal');
expect(isHorizontal).to.be.true;
})
);
it('should keep isHorizontal=true when lane is moved',
inject(function(elementRegistry, modeling) {
// given
var lane = elementRegistry.get('Horizontal_Lane');
// when
modeling.moveElements([ lane ], { x: 0, y: 0 });
// then
var isHorizontal = getDi(lane).get('isHorizontal');
expect(isHorizontal).to.be.true;
})
);
it('should keep isHorizontal=false when lane is moved',
inject(function(elementRegistry, modeling) {
// given
var lane = elementRegistry.get('Vertical_Lane');
// when
modeling.moveElements([ lane ], { x: 0, y: 0 });
// then
var isHorizontal = getDi(lane).get('isHorizontal');
expect(isHorizontal).to.be.false;
})
);
it('should set isHorizontal=true when participant is resized',
inject(function(elementRegistry, modeling) {
// given
var participant = elementRegistry.get('Participant');
// when
modeling.resizeShape(participant, { x: 0, y: 0, width: 10, height: 10 });
// then
var isHorizontal = getDi(participant).get('isHorizontal');
expect(isHorizontal).to.be.true;
})
);
it('should set isHorizontal=true when lane is resized',
inject(function(elementRegistry, modeling) {
// given
var lane = elementRegistry.get('Lane');
// when
modeling.resizeLane(lane, { x: 0, y: 0, width: 10, height: 10 });
// then
var isHorizontal = getDi(lane).get('isHorizontal');
expect(isHorizontal).to.be.true;
})
);
});
describe('never unset on revert', function() {
diagramXML = require('./IsHorizontalFix.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: [
coreModule,
modelingModule
]
}));
it('should not unset isHorizontal=true when participant move action is reverted',
inject(function(commandStack, elementRegistry, modeling) {
// given
var participant = elementRegistry.get('Participant');
modeling.moveElements([ participant ], { x: 0, y: 0 });
// when
commandStack.undo();
// then
var isHorizontal = getDi(participant).get('isHorizontal');
expect(isHorizontal).to.be.true;
})
);
it('should not unset isHorizontal=true when lane move action is reverted',
inject(function(commandStack, elementRegistry, modeling) {
// given
var lane = elementRegistry.get('Lane');
modeling.moveElements([ lane ], { x: 0, y: 0 });
// when
commandStack.undo();
// then
var isHorizontal = getDi(lane).get('isHorizontal');
expect(isHorizontal).to.be.true;
})
);
it('should not unset isHorizontal=true when participant resize action is reverted',
inject(function(commandStack, elementRegistry, modeling) {
// given
var participant = elementRegistry.get('Participant');
modeling.resizeShape(participant, { x: 0, y: 0, width: 10, height: 10 });
// when
commandStack.undo();
// then
var isHorizontal = getDi(participant).get('isHorizontal');
expect(isHorizontal).to.be.true;
})
);
it('should not unset isHorizontal=true when lane resize action is reverted',
inject(function(commandStack, elementRegistry, modeling) {
// given
var lane = elementRegistry.get('Lane');
modeling.resizeLane(lane, { x: 0, y: 0, width: 10, height: 10 });
// when
commandStack.undo();
// then
var isHorizontal = getDi(lane).get('isHorizontal');
expect(isHorizontal).to.be.true;
})
);
});
});
================================================
FILE: test/spec/features/modeling/behavior/LabelBehavior.bpmn
================================================
SequenceFlow_1
SequenceFlow_1
foo
================================================
FILE: test/spec/features/modeling/behavior/LabelBehavior.copyPaste.bpmn
================================================
SequenceFlow
SequenceFlow
================================================
FILE: test/spec/features/modeling/behavior/LabelBehaviorSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import {
resizeBounds
} from 'diagram-js/lib/features/resize/ResizeUtil';
import {
DEFAULT_LABEL_SIZE,
getExternalLabelMid
} from 'lib/util/LabelUtil';
import {
getBusinessObject,
getDi
} from 'lib/util/ModelUtil';
import {
assign,
map,
pick
} from 'min-dash';
import modelingModule from 'lib/features/modeling';
import coreModule from 'lib/core';
import gridSnappingModule from 'lib/features/grid-snapping';
describe('features/modeling/behavior - LabelBehavior', function() {
var diagramXML = require('./LabelBehavior.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: [
modelingModule,
coreModule
]
}));
describe('updating name property', function() {
it('should update label', inject(function(elementRegistry, eventBus, modeling) {
// given
var startEvent = elementRegistry.get('StartEvent_1'),
spy = sinon.spy();
eventBus.once('commandStack.element.updateLabel.execute', spy);
// when
modeling.updateProperties(startEvent, {
name: 'bar'
});
// then
expect(startEvent.businessObject.name).to.equal('bar');
expect(spy).to.have.been.called;
}));
it('should create label', inject(function(elementRegistry, eventBus, modeling) {
// given
var startEvent = elementRegistry.get('ExclusiveGateway_1'),
spy = sinon.spy();
eventBus.once('commandStack.element.updateLabel.execute', spy);
// when
modeling.updateProperties(startEvent, {
name: 'foo'
});
// then
var labelShape = startEvent.label;
expect(labelShape).to.exist;
expect(startEvent.businessObject.name).to.equal('foo');
expect(spy).to.have.been.called;
}));
it('should remove label', inject(function(elementRegistry, modeling) {
// given
var event = elementRegistry.get('StartEvent_1');
// when
modeling.updateProperties(event, {
name: undefined
});
// then
var labelShape = event.label;
expect(labelShape).not.to.exist;
expect(getBusinessObject(event).get('name')).not.to.exist;
}));
});
describe('updating name property via `modeling.updateModdleProperties`', function() {
it('should create label', inject(function(elementRegistry, modeling) {
// given
var gateway = elementRegistry.get('ExclusiveGateway_1'),
bo = getBusinessObject(gateway);
// when
modeling.updateModdleProperties(gateway, bo, {
name: 'foo'
});
// then
var labelShape = gateway.label;
expect(labelShape).to.exist;
expect(gateway.businessObject.name).to.equal('foo');
}));
it('should remove label', inject(function(elementRegistry, modeling) {
// given
var event = elementRegistry.get('StartEvent_1'),
bo = getBusinessObject(event);
// when
modeling.updateModdleProperties(event, bo, {
name: undefined
});
// then
var labelShape = event.label;
expect(labelShape).not.to.exist;
expect(getBusinessObject(event).get('name')).not.to.exist;
}));
it('should NOT create label when message name is added', inject(
function(elementRegistry, modeling) {
// given
var messageEvent = elementRegistry.get('IntermediateCatchEvent_1'),
bo = getBusinessObject(messageEvent);
// when
modeling.updateModdleProperties(messageEvent, bo.eventDefinitions[0].messageRef, {
name: 'foo'
});
// then
var labelShape = messageEvent.label;
expect(labelShape).not.to.exist;
})
);
it('should NOT remove label when message name is removed', inject(
function(elementRegistry, modeling) {
// given
var messageEvent = elementRegistry.get('IntermediateCatchEvent_2'),
bo = getBusinessObject(messageEvent);
// when
modeling.updateModdleProperties(messageEvent, bo.eventDefinitions[0].messageRef, {
name: undefined
});
// then
var labelShape = messageEvent.label;
expect(labelShape).to.exist;
})
);
});
describe('add label', function() {
it('should add to sequence flow with name', inject(
function(bpmnFactory, elementRegistry, modeling) {
// given
var startEvent = elementRegistry.get('StartEvent_1'),
task = elementRegistry.get('Task_1'),
businessObject = bpmnFactory.create('bpmn:SequenceFlow', {
name: 'foo'
});
// when
var connection = modeling.createConnection(startEvent, task, {
type: 'bpmn:SequenceFlow',
businessObject: businessObject
}, startEvent.parent);
// then
expect(connection.label).to.exist;
}
));
it('should NOT add to sequence flow without name', inject(
function(elementRegistry, modeling) {
// given
var startEvent = elementRegistry.get('StartEvent_1'),
task = elementRegistry.get('Task_1');
// when
var connection = modeling.connect(startEvent, task);
// then
expect(connection.label).not.to.exist;
}
));
it('should add to exclusive gateway with name', inject(
function(bpmnFactory, elementFactory, elementRegistry, modeling) {
// given
var parentShape = elementRegistry.get('Process_1'),
businessObject = bpmnFactory.create('bpmn:ExclusiveGateway', {
name: 'foo'
}),
newShapeAttrs = {
type: 'bpmn:ExclusiveGateway',
businessObject: businessObject
};
// when
var newShape = modeling.createShape(newShapeAttrs, { x: 50, y: 50 }, parentShape);
// then
expect(newShape.label).to.exist;
}
));
it('should NOT add to exclusive gateway without name', inject(
function(elementFactory, elementRegistry, modeling) {
// given
var parentShape = elementRegistry.get('Process_1'),
newShapeAttrs = {
type: 'bpmn:ExclusiveGateway'
};
// when
var newShape = modeling.createShape(newShapeAttrs, { x: 50, y: 50 }, parentShape);
// then
expect(newShape.label).not.to.exist;
}
));
it('should add to group', inject(
function(bpmnFactory, elementRegistry, modeling) {
// given
var parentShape = elementRegistry.get('Process_1'),
categoryValue = bpmnFactory.create('bpmn:CategoryValue', {
value: 'foo'
}),
businessObject = bpmnFactory.create('bpmn:Group', {
categoryValueRef: categoryValue
}),
newShapeAttrs = {
type: 'bpmn:Group',
businessObject: businessObject
};
// when
var newShape = modeling.createShape(newShapeAttrs, { x: 50, y: 50 }, parentShape);
// then
expect(newShape.label).to.exist;
}
));
it('should not add to task', inject(
function(elementFactory, elementRegistry, modeling) {
// given
var parentShape = elementRegistry.get('Process_1'),
newShapeAttrs = { type: 'bpmn:Task' };
// when
var newShape = modeling.createShape(newShapeAttrs, { x: 50, y: 50 }, parentShape);
// then
expect(newShape.label).not.to.exist;
}
));
it('should not add label if created shape is label', inject(
function(bpmnFactory, elementFactory, elementRegistry, modeling, textRenderer) {
// given
var parentShape = elementRegistry.get('Process_1');
var createLabelSpy = sinon.spy(modeling, 'createLabel');
var exclusiveGatewayBo = bpmnFactory.create('bpmn:ExclusiveGateway', {
name: 'Foo'
});
var exclusiveGateway = elementFactory.createShape({
type: 'bpmn:ExclusiveGateway',
businessObject: exclusiveGatewayBo
});
modeling.createElements([ exclusiveGateway ], { x: 50, y: 50 }, parentShape, {
createElementsBehavior: false
});
var externalLabelMid = getExternalLabelMid(exclusiveGateway);
var externalLabelBounds = textRenderer.getExternalLabelBounds(DEFAULT_LABEL_SIZE, 'Foo');
var label = elementFactory.createLabel({
businessObject: exclusiveGatewayBo,
labelTarget: exclusiveGateway,
width: externalLabelBounds.width,
height: externalLabelBounds.height
});
// when
modeling.createElements([ label ], externalLabelMid, parentShape);
// then
expect(createLabelSpy).not.to.have.been.called;
})
);
describe('on append', function() {
it('correctly wired and positioned', inject(
function(bpmnFactory, elementRegistry, modeling, commandStack) {
// given
var startEventShape = elementRegistry.get('StartEvent_1'),
businessObject = bpmnFactory.create('bpmn:EndEvent', {
name: 'foo'
});
// when
var targetShape = modeling.appendShape(startEventShape, {
type: 'bpmn:EndEvent',
businessObject: businessObject
});
var label = targetShape.label;
// then
expect(label).to.exist;
expect(elementRegistry.get(label.id)).to.exist;
expect(label.x).to.closeTo(299, 1);
expect(label.y).to.be.closeTo(145, 1);
expect(label.width).to.be.within(15, 18);
expect(label.height).to.be.within(13, 15);
}
));
it('with di', inject(
function(bpmnFactory, elementRegistry, modeling, commandStack) {
// given
var startEventShape = elementRegistry.get('StartEvent_1'),
businessObject = bpmnFactory.create('bpmn:EndEvent', {
name: 'foo'
});
// when
var targetShape = modeling.appendShape(startEventShape, {
type: 'bpmn:EndEvent',
businessObject: businessObject
}),
targetDi = getDi(targetShape);
// then
expect(targetDi.label).to.exist;
expect(targetDi.label).to.have.bounds(targetShape.label);
}
));
});
it('should add with di', inject(
function(bpmnFactory, elementFactory, elementRegistry, modeling) {
// given
var startEventShape = elementRegistry.get('StartEvent_1'),
businessObject = bpmnFactory.create('bpmn:SequenceFlow', {
name: 'foo'
});
// when
var targetShape = modeling.appendShape(startEventShape, {
type: 'bpmn:EndEvent',
businessObject: businessObject
}),
targetDi = getDi(targetShape);
// then
expect(targetDi.label).to.exist;
expect(targetDi.label).to.have.bounds(targetShape.label);
}
));
it('should NOT add label if hint createElementsBehavior=false', inject(
function(bpmnFactory, elementFactory, elementRegistry, modeling) {
// given
var parentShape = elementRegistry.get('Process_1'),
newShape = elementFactory.createShape({
type: 'bpmn:ExclusiveGateway',
businessObject: bpmnFactory.create('bpmn:ExclusiveGateway', {
name: 'foo'
})
});
// when
newShape = modeling.createShape(newShape, { x: 50, y: 50 }, parentShape, {
createElementsBehavior: false
});
// then
expect(newShape.label).not.to.exist;
}
));
});
describe('move label', function() {
it('should move start event label', inject(function(elementRegistry, modeling) {
// given
var startEventShape = elementRegistry.get('StartEvent_1'),
startEventDi = getDi(startEventShape),
labelShape = startEventShape.label;
// when
modeling.moveElements([ labelShape ], { x: 10, y: -10 });
// then
expect(labelShape.x).to.be.within(193, 194);
expect(labelShape.y).to.equal(128);
expect(startEventDi.label.bounds.x).to.be.within(193, 194);
expect(startEventDi.label.bounds.y).to.equal(128);
}));
describe('connection labels', function() {
it('should center position visible', inject(
function(bpmnFactory, elementRegistry, modeling) {
// given
var startEventShape = elementRegistry.get('StartEvent_1'),
taskShape = elementRegistry.get('Task_1'),
businessObject = bpmnFactory.create('bpmn:SequenceFlow', {
name: 'foo'
});
var sequenceFlowConnection = modeling.createConnection(startEventShape, taskShape, {
type: 'bpmn:SequenceFlow',
businessObject: businessObject
}, startEventShape.parent);
// when
sequenceFlowConnection.label.hidden = false;
modeling.updateWaypoints(sequenceFlowConnection, [
sequenceFlowConnection.waypoints[0],
{
x: sequenceFlowConnection.waypoints[0].x,
y: 200
},
{
x: sequenceFlowConnection.waypoints[1].x,
y: 200
},
sequenceFlowConnection.waypoints[1]
]);
// then
expect(sequenceFlowConnection.label.x).to.be.closeTo(273, 1);
expect(sequenceFlowConnection.label.y).to.be.closeTo(182, 1);
}
));
it('should NOT move label if labelBehavior=false', inject(function(elementRegistry, modeling) {
// given
var connection = elementRegistry.get('SequenceFlow_1'),
waypoints = copyWaypoints(connection),
label = connection.label,
oldLabelPosition = pick(label, [ 'x', 'y' ]);
var newWaypoints = [
waypoints[ 0 ],
{ x: 0, y: 0 },
waypoints[ 1 ]
];
// when
modeling.updateWaypoints(connection, newWaypoints, { labelBehavior: false });
// then
expect(pick(label, [ 'x', 'y' ])).to.eql(oldLabelPosition);
}));
});
});
describe('delete label', function() {
it('should remove name', inject(function(elementRegistry, modeling) {
// given
var startEventShape = elementRegistry.get('StartEvent_1'),
startEvent = startEventShape.businessObject,
labelShape = startEventShape.label;
// when
modeling.removeShape(labelShape);
// then
expect(startEventShape.label).not.to.exist;
expect(startEvent.name).not.to.exist;
}));
});
describe('update properties', function() {
it('should resize after updating name property', inject(
function(elementRegistry, modeling) {
// given
var spy = sinon.spy(modeling, 'resizeShape');
var startEventShape = elementRegistry.get('StartEvent_1');
// when
modeling.updateProperties(startEventShape, { name: 'bar' });
// then
expect(spy).to.have.been.called;
}
));
it('should resize after updating text property', inject(
function(elementRegistry, modeling) {
// given
var spy = sinon.spy(modeling, 'resizeShape');
var textAnnotationShape = elementRegistry.get('TextAnnotation_1');
// when
modeling.updateProperties(textAnnotationShape, { text: 'bar' });
// then
expect(spy).to.have.been.called;
}
));
});
describe('resize label target', function() {
describe('should move label (outside)', function() {
it('to the top', inject(function(elementRegistry, modeling) {
// given
var groupShape = elementRegistry.get('Group_2'),
label = groupShape.label,
labelBounds = getBounds(label);
// when
modeling.resizeShape(
groupShape,
resizeBounds(groupShape, 'se', {
x: 0,
y: -50
})
);
// then
expect(label.x).to.equal(labelBounds.x);
expect(label.y).to.be.below(labelBounds.y);
}));
it('to the right', inject(function(elementRegistry, modeling) {
// given
var groupShape = elementRegistry.get('Group_1'),
label = groupShape.label,
labelBounds = getBounds(label);
// when
modeling.resizeShape(
groupShape,
resizeBounds(groupShape, 'se', {
x: 50,
y: 0
})
);
// then
expect(label.x).to.be.above(labelBounds.x);
expect(label.y).to.equal(labelBounds.y);
}));
it('to the bottom', inject(function(elementRegistry, modeling) {
// given
var groupShape = elementRegistry.get('Group_2'),
label = groupShape.label,
labelBounds = getBounds(label);
// when
modeling.resizeShape(
groupShape,
resizeBounds(groupShape, 'se', {
x: 0,
y: 50
})
);
// then
expect(label.x).to.equal(labelBounds.x);
expect(label.y).to.be.above(labelBounds.y);
}));
it('to the left', inject(function(elementRegistry, modeling) {
// given
var groupShape = elementRegistry.get('Group_3'),
label = groupShape.label,
labelBounds = getBounds(label);
// when
modeling.resizeShape(
groupShape,
resizeBounds(groupShape, 'sw', {
x: -50,
y: 0
})
);
// then
expect(label.x).to.be.below(labelBounds.x);
expect(label.y).to.equal(labelBounds.y);
}));
it('NOT if reference point not affected', inject(function(elementRegistry, modeling) {
// given
var groupShape = elementRegistry.get('Group_2'),
label = groupShape.label,
labelBounds = getBounds(label);
// when
modeling.resizeShape(
groupShape,
resizeBounds(groupShape, 'ne', {
x: 0,
y: -50
})
);
// then
expect(getBounds(label)).to.eql(labelBounds);
}));
});
describe('should move label (inside)', function() {
it('to the top', inject(function(elementRegistry, modeling) {
// given
var groupShape = elementRegistry.get('Group_4'),
label = groupShape.label,
labelBounds = getBounds(label);
// when
modeling.resizeShape(
groupShape,
resizeBounds(groupShape, 'nw', {
x: 0,
y: -50
})
);
// then
expect(label.x).to.equal(labelBounds.x);
expect(label.y).to.be.below(labelBounds.y);
}));
it('to the right', inject(function(elementRegistry, modeling) {
// given
var groupShape = elementRegistry.get('Group_4'),
label = groupShape.label,
labelBounds = getBounds(label);
// when
modeling.resizeShape(
groupShape,
resizeBounds(groupShape, 'ne', {
x: 50,
y: 0
})
);
// then
expect(label.x).to.be.above(labelBounds.x);
expect(label.y).to.equal(labelBounds.y);
}));
it('to the bottom', inject(function(elementRegistry, modeling) {
// given
var groupShape = elementRegistry.get('Group_5'),
label = groupShape.label,
labelBounds = getBounds(label);
// when
modeling.resizeShape(
groupShape,
resizeBounds(groupShape, 'sw', {
x: 0,
y: 50
})
);
// then
expect(label.x).to.equal(labelBounds.x);
expect(label.y).to.be.above(labelBounds.y);
}));
it('to the left', inject(function(elementRegistry, modeling) {
// given
var groupShape = elementRegistry.get('Group_4'),
label = groupShape.label,
labelBounds = getBounds(label);
// when
modeling.resizeShape(
groupShape,
resizeBounds(groupShape, 'sw', {
x: -50,
y: 0
})
);
// then
expect(label.x).to.be.below(labelBounds.x);
expect(label.y).to.equal(labelBounds.y);
}));
it('NOT if reference point not affected', inject(function(elementRegistry, modeling) {
// given
var groupShape = elementRegistry.get('Group_4'),
label = groupShape.label,
labelBounds = getBounds(label);
// when
modeling.resizeShape(
groupShape,
resizeBounds(groupShape, 'se', {
x: 0,
y: 50
})
);
// then
expect(getBounds(label)).to.eql(labelBounds);
}));
});
});
});
describe('features/modeling/behavior - LabelBehavior - copy/paste integration', function() {
var diagramXML = require('./LabelBehavior.copyPaste.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: [
modelingModule,
coreModule,
gridSnappingModule
]
}));
it('should skip adjustment during creation', inject(
function(elementRegistry, copyPaste, canvas, dragging) {
// given
var elements = [
elementRegistry.get('Source'),
elementRegistry.get('Target'),
elementRegistry.get('SequenceFlow'),
elementRegistry.get('SequenceFlow').label
];
var rootElement = canvas.getRootElement();
copyPaste.copy(elements);
// when
var pastedElements = copyPaste.paste({
element: rootElement,
point: {
x: 700,
y: 300
}
});
var label = pastedElements[3];
// then
expect(label).to.exist;
expect(label).to.have.position({ x: 681, y: 287 });
}
));
});
// helpers //////////
function copyWaypoint(waypoint) {
return assign({}, waypoint);
}
function copyWaypoints(connection) {
return map(connection.waypoints, function(waypoint) {
waypoint = copyWaypoint(waypoint);
if (waypoint.original) {
waypoint.original = copyWaypoint(waypoint.original);
}
return waypoint;
});
}
function getBounds(element) {
return pick(element, [ 'x', 'y', 'width', 'height' ]);
}
================================================
FILE: test/spec/features/modeling/behavior/LayoutConnectionBehavior.bpmn
================================================
SequenceFlow_1
SequenceFlow_1
================================================
FILE: test/spec/features/modeling/behavior/LayoutConnectionBehaviorSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import {
assign,
map,
} from 'min-dash';
import modelingModule from 'lib/features/modeling';
import coreModule from 'lib/core';
describe('behavior - LayoutConnectionBehavior', function() {
var diagramXML = require('./LayoutConnectionBehavior.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: [
modelingModule,
coreModule
]
}));
describe('incomming association', function() {
it('should reconnect on sequence flow bendpoint move', inject(function(elementRegistry, modeling) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow_1');
var association = elementRegistry.get('Association_1');
// when
var newWaypoints = copyWaypoints(sequenceFlow);
newWaypoints.splice(1, 0,
{ x: 500, y: 300 }
);
var hints = {
bendpointMove: {
bendpointIndex: 1,
insert: true
}
};
modeling.updateWaypoints(sequenceFlow, newWaypoints, hints);
// then
expectApproximateWaypoints(association, [
{ x: 525, y: 110 },
{ x: 355, y: 229 },
]);
}));
it('should reconnect on sequence flow connection move', inject(function(elementRegistry, modeling) {
// given
var startEvent = elementRegistry.get('StartEvent_1');
var endEvent = elementRegistry.get('EndEvent_1');
var association = elementRegistry.get('Association_1');
// when
modeling.moveElements([ startEvent, endEvent ], { x: 0, y: 200 });
// then
expectApproximateWaypoints(association, [
{ x: 525, y: 110 },
{ x: 460, y: 350 },
]);
}));
it('should reconnect on sequence flow waypoint update', inject(function(elementRegistry, modeling) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow_1');
var association = elementRegistry.get('Association_1');
// when
var newWaypoints = [
sequenceFlow.waypoints[0],
{ x: sequenceFlow.waypoints[0].x, y: 300 },
{ x: sequenceFlow.waypoints[1].x, y: 300 },
sequenceFlow.waypoints[1],
];
modeling.updateWaypoints(sequenceFlow, newWaypoints);
// then
expectApproximateWaypoints(association, [
{ x: 525, y: 110 },
{ x: 460, y: 300 }
]);
}));
it('should reconnect on message flow bendpoint move', inject(function(elementRegistry, modeling) {
// given
var messageFlow = elementRegistry.get('MessageFlow_1');
var association = elementRegistry.get('Association_4');
// when
var newWaypoints = copyWaypoints(messageFlow);
newWaypoints.splice(1, 0,
{ x: 200, y: 500 }
);
var hints = {
bendpointMove: {
bendpointIndex: 1,
insert: true
}
};
modeling.updateWaypoints(messageFlow, newWaypoints, hints);
// then
expectApproximateWaypoints(association, [
{ x: 353, y: 540 },
{ x: 240, y: 595 },
]);
}));
it('should reconnect on message flow connection move', inject(function(elementRegistry, modeling) {
// given
var startParticipant = elementRegistry.get('Participant_1');
var endParticipant = elementRegistry.get('Participant_2');
var association = elementRegistry.get('Association_4');
// when
modeling.moveElements([ startParticipant, endParticipant ], { x: 0, y: 200 });
// then
expectApproximateWaypoints(association, [
{ x: 353, y: 540 },
{ x: 280, y: 700 },
]);
}));
it('should reconnect on message flow waypoint update', inject(function(elementRegistry, modeling) {
// given
var messageFlow = elementRegistry.get('MessageFlow_1');
var association = elementRegistry.get('Association_4');
// when
var newWaypoints = [
messageFlow.waypoints[0],
{ x: 230, y: 670 },
{ x: 230, y: 330 },
messageFlow.waypoints[1],
];
modeling.updateWaypoints(messageFlow, newWaypoints);
// then
expectApproximateWaypoints(association, [
{ x: 353, y: 540 },
{ x: 230, y: 500 }
]);
}));
});
describe('outgoing association', function() {
it('should reconnect on sequence flow bendpoint move', inject(function(elementRegistry, modeling) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow_1');
var association = elementRegistry.get('Association_2');
// when
var newWaypoints = copyWaypoints(sequenceFlow);
newWaypoints.splice(1, 0,
{ x: 500, y: 300 }
);
var hints = {
bendpointMove: {
bendpointIndex: 1,
insert: true
}
};
modeling.updateWaypoints(sequenceFlow, newWaypoints, hints);
// then
expectApproximateWaypoints(association, [
{ x: 355, y: 229 },
{ x: 525, y: 110 }
]);
}));
it('should reconnect on sequence flow connection move', inject(function(elementRegistry, modeling) {
// given
var startEvent = elementRegistry.get('StartEvent_1');
var endEvent = elementRegistry.get('EndEvent_1');
var association = elementRegistry.get('Association_2');
// when
modeling.moveElements([ startEvent, endEvent ], { x: 0, y: 200 });
// then
expectApproximateWaypoints(association, [
{ x: 460, y: 350 },
{ x: 525, y: 110 }
]);
}));
it('should reconnect on sequence flow waypoint update', inject(function(elementRegistry, modeling) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow_1');
var association = elementRegistry.get('Association_2');
// when
var newWaypoints = [
sequenceFlow.waypoints[0],
{ x: sequenceFlow.waypoints[0].x, y: 300 },
{ x: sequenceFlow.waypoints[1].x, y: 300 },
sequenceFlow.waypoints[1],
];
modeling.updateWaypoints(sequenceFlow, newWaypoints);
// then
expectApproximateWaypoints(association, [
{ x: 460, y: 300 },
{ x: 525, y: 110 }
]);
}));
it('should reconnect on message flow bendpoint move', inject(function(elementRegistry, modeling) {
// given
var messageFlow = elementRegistry.get('MessageFlow_1');
var association = elementRegistry.get('Association_3');
// when
var newWaypoints = copyWaypoints(messageFlow);
newWaypoints.splice(1, 0,
{ x: 200, y: 500 }
);
var hints = {
bendpointMove: {
bendpointIndex: 1,
insert: true
}
};
modeling.updateWaypoints(messageFlow, newWaypoints, hints);
// then
expectApproximateWaypoints(association, [
{ x: 240, y: 595 },
{ x: 353, y: 540 }
]);
}));
it('should reconnect on message flow connection move', inject(function(elementRegistry, modeling) {
// given
var startParticipant = elementRegistry.get('Participant_1');
var endParticipant = elementRegistry.get('Participant_2');
var association = elementRegistry.get('Association_3');
// when
modeling.moveElements([ startParticipant, endParticipant ], { x: 0, y: 200 });
// then
expectApproximateWaypoints(association, [
{ x: 280, y: 700 },
{ x: 353, y: 540 }
]);
}));
it('should reconnect on message flow waypoint update', inject(function(elementRegistry, modeling) {
// given
var messageFlow = elementRegistry.get('MessageFlow_1');
var association = elementRegistry.get('Association_3');
// when
var newWaypoints = [
messageFlow.waypoints[0],
{ x: 230, y: 670 },
{ x: 230, y: 330 },
messageFlow.waypoints[1],
];
modeling.updateWaypoints(messageFlow, newWaypoints);
// then
expectApproximateWaypoints(association, [
{ x: 230, y: 500 },
{ x: 353, y: 540 }
]);
}));
});
});
// helpers //////////
function copyWaypoint(waypoint) {
return assign({}, waypoint);
}
function copyWaypoints(connection) {
return map(connection.waypoints, function(waypoint) {
waypoint = copyWaypoint(waypoint);
if (waypoint.original) {
waypoint.original = copyWaypoint(waypoint.original);
}
return waypoint;
});
}
function expectApproximateWaypoints(connection, expectedWaypoints) {
var actualWaypoints = connection.waypoints;
expect(actualWaypoints).to.exist;
expect(expectedWaypoints).to.exist;
expect(connection.waypoints.length).to.eql(expectedWaypoints.length);
for (var i in actualWaypoints) {
expect(actualWaypoints[i].x).to.be.closeTo(expectedWaypoints[i].x, 1);
expect(actualWaypoints[i].y).to.be.closeTo(expectedWaypoints[i].y, 1);
}
}
================================================
FILE: test/spec/features/modeling/behavior/MessageFlowBehavior.bpmn
================================================
================================================
FILE: test/spec/features/modeling/behavior/MessageFlowBehaviorSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import coreModule from 'lib/core';
import modelingModule from 'lib/features/modeling';
describe('features/modeling - message flow behavior', function() {
var testModules = [ coreModule, modelingModule ];
describe('when collapsing participant', function() {
var processDiagramXML = require('./MessageFlowBehavior.bpmn');
beforeEach(bootstrapModeler(processDiagramXML, { modules: testModules }));
it('should reconnect message flows to collapsed participant (incoming)', inject(
function(bpmnReplace, elementRegistry) {
// given
var participant = elementRegistry.get('Participant_1');
// when
participant = bpmnReplace.replaceElement(participant, {
type: 'bpmn:Participant',
isExpanded: false
});
// then
expect(participant.incoming).to.have.length(2);
expect(elementRegistry.get('Flow_1').waypoints).to.eql([
{
original: {
x: 350,
y: 520
},
x: 350,
y: 480
},
{
original: {
x: 350,
y: 110
},
x: 350,
y: 140
}
]);
expect(elementRegistry.get('Flow_2').waypoints).to.eql([
{
original: {
x: 790,
y: 520
},
x: 790,
y: 480
},
{
x: 790,
y: 360
},
{
x: 370,
y: 360
},
{
original: {
x: 370,
y: 110
},
x: 370,
y: 140
}
]);
}
));
it('should reconnect message flows to collapsed participant (outgoing)', inject(
function(bpmnReplace, elementRegistry) {
// given
var participant = elementRegistry.get('Participant_4');
// when
participant = bpmnReplace.replaceElement(participant, {
type: 'bpmn:Participant',
isExpanded: false
});
// then
expect(participant.outgoing).to.have.length(2);
expect(elementRegistry.get('Flow_3').waypoints).to.eql([
{
original: {
x: 780,
y: 750
},
x: 780,
y: 720
},
{
x: 780,
y: 680
},
{
x: 360,
y: 680
},
{
original: {
x: 360,
y: 520
},
x: 360,
y: 560
}
]);
expect(elementRegistry.get('Flow_4').waypoints).to.eql([
{
original: {
x: 800,
y: 750
},
x: 800,
y: 720
},
{
original: {
x: 800,
y: 520
},
x: 800,
y: 560
}
]);
}
));
});
});
================================================
FILE: test/spec/features/modeling/behavior/NonInterruptingBehavior.bpmn
================================================
================================================
FILE: test/spec/features/modeling/behavior/NonInterruptingBehaviorSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import coreModule from 'lib/core';
import modelingModule from 'lib/features/modeling';
describe('features/modeling - non interrupting behavior', function() {
const testModules = [ coreModule, modelingModule ];
const processDiagramXML = require('./NonInterruptingBehavior.bpmn');
beforeEach(bootstrapModeler(processDiagramXML, { modules: testModules }));
let event;
describe('start events', function() {
describe('interrupting', function() {
beforeEach(inject(function(elementRegistry) {
event = elementRegistry.get('StartEvent_interrupting');
}));
it('should stay interrupting when replacing',
inject(function(bpmnReplace) {
// when
const newEvent = bpmnReplace.replaceElement(event, {
type: 'bpmn:StartEvent',
eventDefinitionType: 'bpmn:TimerEventDefinition'
});
// then
expect(newEvent.businessObject.isInterrupting).to.be.true;
})
);
it('should become non-interrupting when explicitly replacing with non-interrupting event',
inject(function(bpmnReplace) {
// when
const newEvent = bpmnReplace.replaceElement(event, {
type: 'bpmn:StartEvent',
eventDefinitionType: 'bpmn:TimerEventDefinition',
isInterrupting: false
});
// then
expect(newEvent.businessObject.isInterrupting).to.be.false;
})
);
});
describe('non-interrupting', function() {
beforeEach(inject(function(elementRegistry) {
event = elementRegistry.get('StartEvent_nonInterrupting');
}));
it('should stay non-interrupting when replacing',
inject(function(bpmnReplace) {
// when
const newEvent = bpmnReplace.replaceElement(event, {
type: 'bpmn:StartEvent',
eventDefinitionType: 'bpmn:TimerEventDefinition'
});
// then
expect(newEvent.businessObject.isInterrupting).to.be.false;
})
);
it('should become interrupting when explicitly replacing with interrupting event',
inject(function(bpmnReplace) {
// when
const newEvent = bpmnReplace.replaceElement(event, {
type: 'bpmn:StartEvent',
eventDefinitionType: 'bpmn:TimerEventDefinition',
isInterrupting: true
});
// then
expect(newEvent.businessObject.isInterrupting).to.be.true;
})
);
it('should become interrupting when replacing with interrupting event type',
inject(function(bpmnReplace) {
// when
const newEvent = bpmnReplace.replaceElement(event, {
type: 'bpmn:StartEvent',
eventDefinitionType: 'bpmn:ErrorEventDefinition'
});
// then
expect(newEvent.businessObject.isInterrupting).to.be.true;
})
);
});
});
describe('boundary events', function() {
describe('interrupting', function() {
beforeEach(inject(function(elementRegistry) {
event = elementRegistry.get('BoundaryEvent_interrupting');
}));
it('should stay interrupting when replacing',
inject(function(bpmnReplace) {
// when
const newEvent = bpmnReplace.replaceElement(event, {
type: 'bpmn:BoundaryEvent',
eventDefinitionType: 'bpmn:TimerEventDefinition'
});
// then
expect(newEvent.businessObject.cancelActivity).to.be.true;
})
);
it('should become non-interrupting when explicitly replacing with non-interrupting event',
inject(function(bpmnReplace) {
// when
const newEvent = bpmnReplace.replaceElement(event, {
type: 'bpmn:BoundaryEvent',
eventDefinitionType: 'bpmn:TimerEventDefinition',
cancelActivity: false
});
// then
expect(newEvent.businessObject.cancelActivity).to.be.false;
})
);
});
describe('non-interrupting', function() {
beforeEach(inject(function(elementRegistry) {
event = elementRegistry.get('BoundaryEvent_nonInterrupting');
}));
it('should stay non-interrupting when replacing',
inject(function(bpmnReplace) {
// when
const newEvent = bpmnReplace.replaceElement(event, {
type: 'bpmn:BoundaryEvent',
eventDefinitionType: 'bpmn:TimerEventDefinition'
});
// then
expect(newEvent.businessObject.cancelActivity).to.be.false;
})
);
it('should become interrupting when explicitly replacing with interrupting event',
inject(function(bpmnReplace) {
// when
const newEvent = bpmnReplace.replaceElement(event, {
type: 'bpmn:BoundaryEvent',
eventDefinitionType: 'bpmn:TimerEventDefinition',
cancelActivity: true
});
// then
expect(newEvent.businessObject.cancelActivity).to.be.true;
})
);
it('should become interrupting when replacing with interrupting event type',
inject(function(bpmnReplace) {
// when
const newEvent = bpmnReplace.replaceElement(event, {
type: 'bpmn:BoundaryEvent',
eventDefinitionType: 'bpmn:ErrorEventDefinition'
});
// then
expect(newEvent.businessObject.cancelActivity).to.be.true;
})
);
});
});
});
================================================
FILE: test/spec/features/modeling/behavior/ReconnectConnection.data-association.bpmn
================================================
DataObjectReference_1
DataObjectReference_1
================================================
FILE: test/spec/features/modeling/behavior/ReconnectConnectionSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import modelingModule from 'lib/features/modeling';
import coreModule from 'lib/core';
describe('features/modeling - reconnect connection', function() {
var testModules = [ coreModule, modelingModule ];
var processDiagramXML = require('./ReconnectConnection.data-association.bpmn');
beforeEach(bootstrapModeler(processDiagramXML, { modules: testModules }));
var task1Shape,
task2Shape,
dataInputAssociation,
dataOutputAssociation,
newWaypoints;
beforeEach(inject(function(elementRegistry) {
task1Shape = elementRegistry.get('Task_1');
task2Shape = elementRegistry.get('Task_2');
dataInputAssociation = elementRegistry.get('DataInputAssociation_1');
dataOutputAssociation = elementRegistry.get('DataOutputAssociation_1');
}));
describe('reconnect DataOutputAssociations', function() {
beforeEach(function() {
newWaypoints = [ { x: task2Shape.x, y: task2Shape.y + 30 }, dataOutputAssociation.waypoints[1] ];
});
it('should execute', inject(function(modeling) {
// when
modeling.reconnectStart(dataOutputAssociation, task2Shape, newWaypoints);
// then
expect(task1Shape.businessObject.dataOutputAssociations).to.be.empty;
expect(task2Shape.businessObject.dataOutputAssociations).to.include(dataOutputAssociation.businessObject);
}));
it('should undo', inject(function(modeling, commandStack) {
// when
modeling.reconnectStart(dataOutputAssociation, task2Shape, newWaypoints);
commandStack.undo();
// then
expect(task1Shape.businessObject.dataOutputAssociations).to.include(dataOutputAssociation.businessObject);
expect(task2Shape.businessObject.dataOutputAssociations).to.be.empty;
}));
it('should redo', inject(function(modeling, commandStack) {
// when
modeling.reconnectStart(dataOutputAssociation, task2Shape, newWaypoints);
commandStack.undo();
commandStack.redo();
// then
expect(task1Shape.businessObject.dataOutputAssociations).to.be.empty;
expect(task2Shape.businessObject.dataOutputAssociations).to.include(dataOutputAssociation.businessObject);
}));
});
describe('reconnect DataInputAssociations', function() {
beforeEach(function() {
newWaypoints = [ dataInputAssociation.waypoints[0], { x: task1Shape.x, y: task1Shape.y - 30 } ];
});
it('should execute', inject(function(modeling) {
// when
modeling.reconnectEnd(dataInputAssociation, task1Shape, newWaypoints);
// then
expect(task1Shape.businessObject.dataInputAssociations).to.include(dataInputAssociation.businessObject);
expect(task2Shape.businessObject.dataInputAssociations).to.be.empty;
}));
it('should undo', inject(function(modeling, commandStack) {
// when
modeling.reconnectEnd(dataInputAssociation, task1Shape, newWaypoints);
commandStack.undo();
// then
expect(task1Shape.businessObject.dataInputAssociations).to.be.empty;
expect(task2Shape.businessObject.dataInputAssociations).to.include(dataInputAssociation.businessObject);
}));
it('should redo', inject(function(modeling, commandStack) {
// when
modeling.reconnectEnd(dataInputAssociation, task1Shape, newWaypoints);
commandStack.undo();
commandStack.redo();
// then
expect(task1Shape.businessObject.dataInputAssociations).to.include(dataInputAssociation.businessObject);
expect(task2Shape.businessObject.dataInputAssociations).to.be.empty;
}));
});
});
================================================
FILE: test/spec/features/modeling/behavior/RemoveElementBehavior.bpmn
================================================
SequenceFlow1
SequenceFlow1
SequenceFlow2
SequenceFlow2
SequenceFlow3
DataStoreReference1
SequenceFlow3
SequenceFlow4
SequenceFlow4
SequenceFlow6
SequenceFlow5
SequenceFlow6
SequenceFlow5
SequenceFlow7
SequenceFlow7
SequenceFlow8
SequenceFlow8
================================================
FILE: test/spec/features/modeling/behavior/RemoveElementBehavior.diagonal.bpmn
================================================
SequenceFlow1
SequenceFlow1
SequenceFlow2
SequenceFlow2
================================================
FILE: test/spec/features/modeling/behavior/RemoveElementBehavior.perpendicular.bpmn
================================================
SequenceFlow_1
SequenceFlow_1
SequenceFlow_2
SequenceFlow_7
SequenceFlow_7
SequenceFlow_8
SequenceFlow_8
SequenceFlow_Diagonal
SequenceFlow_3
SequenceFlow_3
SequenceFlow_4
SequenceFlow_4
SequenceFlow_5
SequenceFlow_5
SequenceFlow_6
SequenceFlow_2
SequenceFlow_Diagonal
SequenceFlow_6
================================================
FILE: test/spec/features/modeling/behavior/RemoveElementBehavior.vertical.diagonal.bpmn
================================================
SequenceFlow1
SequenceFlow1
SequenceFlow2
SequenceFlow2
================================================
FILE: test/spec/features/modeling/behavior/RemoveElementBehaviorSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import modelingModule from 'lib/features/modeling';
import coreModule from 'lib/core';
describe('features/modeling - remove element behavior', function() {
var testModules = [ coreModule, modelingModule ];
describe('combine sequence flow when deleting element', function() {
describe('parallel connections', function() {
var processDiagramXML = require('./RemoveElementBehavior.bpmn');
beforeEach(bootstrapModeler(processDiagramXML, { modules: testModules }));
it('horizontal', inject(function(modeling, elementRegistry) {
// given
var task = elementRegistry.get('Task1');
// when
modeling.removeShape(task);
// then
var sequenceFlow1 = elementRegistry.get('SequenceFlow1');
var waypoints = sequenceFlow1.waypoints;
// SequenceFlow2 should be deleted
expect(elementRegistry.get(task.id)).to.be.undefined;
expect(sequenceFlow1).not.to.be.undefined;
expect(elementRegistry.get('SequenceFlow2')).to.be.undefined;
// source and target have one connection each
expect(elementRegistry.get('StartEvent1').outgoing.length).to.be.equal(1);
expect(elementRegistry.get('EndEvent1').incoming.length).to.be.equal(1);
// connection has two horizontally equal waypoints
expect(waypoints).to.have.length(2);
expect(waypoints[0].y).to.eql(waypoints[1].y);
}));
it('vertical', inject(function(modeling, elementRegistry) {
// given
var task = elementRegistry.get('Task4');
// when
modeling.removeShape(task);
// then
var waypoints = elementRegistry.get('SequenceFlow7').waypoints;
// connection has two vertically equal waypoints
expect(waypoints).to.have.length(2);
expect(waypoints[0].x).to.eql(waypoints[1].x);
}));
});
describe('perpendicular connections', function() {
var gatewayDiagramXML = require('./RemoveElementBehavior.perpendicular.bpmn');
beforeEach(bootstrapModeler(gatewayDiagramXML, { modules: testModules }));
it('right-down', inject(function(modeling, elementRegistry) {
// given
var task = elementRegistry.get('Task_2');
var mid = {
x : task.x + task.width / 2,
y : task.y + task.height / 2
};
// when
modeling.removeShape(task);
// then
var waypoints = elementRegistry.get('SequenceFlow_1').waypoints;
expect(waypoints).to.have.length(3);
var intersec = waypoints[1];
expect(intersec).to.eql(point(mid));
}));
it('right-up', inject(function(modeling, elementRegistry) {
// given
var task = elementRegistry.get('Task_11');
var mid = {
x : task.x + task.width / 2,
y : task.y + task.height / 2
};
// when
modeling.removeShape(task);
// then
var waypoints = elementRegistry.get('SequenceFlow_7').waypoints;
expect(waypoints).to.have.length(3);
var intersec = waypoints[1];
expect(intersec).to.eql(point(mid));
}));
it('down-right', inject(function(modeling, elementRegistry) {
// given
var task = elementRegistry.get('Task_5');
var mid = {
x : task.x + task.width / 2,
y : task.y + task.height / 2
};
// when
modeling.removeShape(task);
// then
var waypoints = elementRegistry.get('SequenceFlow_3').waypoints;
expect(waypoints).to.have.length(3);
var intersec = waypoints[1];
expect(intersec).to.eql(point(mid));
}));
it('up-right', inject(function(modeling, elementRegistry) {
// given
var task = elementRegistry.get('Task_8');
var mid = {
x : task.x + task.width / 2,
y : task.y + task.height / 2
};
// when
modeling.removeShape(task);
// then
var waypoints = elementRegistry.get('SequenceFlow_5').waypoints;
expect(waypoints).to.have.length(3);
var intersec = waypoints[1];
expect(intersec).to.eql(point(mid));
}));
it('diagonal', inject(function(modeling, elementRegistry) {
// given
var task = elementRegistry.get('Task_4');
var mid = {
x: task.x + task.width / 2,
y: 211
};
// when
modeling.removeShape(task);
// then
var waypoints = elementRegistry.get('SequenceFlow_Diagonal').waypoints;
expect(waypoints).to.have.length(3);
var intersec = waypoints[1];
expect(intersec).to.eql(point(mid));
}));
});
describe('connection layouting', function() {
var processDiagramXML = require('./RemoveElementBehavior.diagonal.bpmn');
beforeEach(bootstrapModeler(processDiagramXML, { modules: testModules }));
it('should execute', inject(function(modeling, elementRegistry) {
// given
var task = elementRegistry.get('Task1');
var sequenceFlow1 = elementRegistry.get('SequenceFlow1');
// when
modeling.removeShape(task);
// then
var waypoints = sequenceFlow1.waypoints;
// SequenceFlow2 should be deleted
expect(elementRegistry.get('SequenceFlow2')).to.be.undefined;
expect(elementRegistry.get(task.id)).to.be.undefined;
// source and target have one connection each
expect(elementRegistry.get('StartEvent1').outgoing.length).to.be.equal(1);
expect(elementRegistry.get('EndEvent1').incoming.length).to.be.equal(1);
// connection has Manhattan layout
expect(waypoints).to.have.length(4);
expect(waypoints[0].y).to.eql(waypoints[1].y);
expect(waypoints[1].x).to.eql(waypoints[2].x);
expect(waypoints[2].y).to.eql(waypoints[3].y);
}));
it('should redo', inject(function(commandStack, modeling, elementRegistry) {
// given
var task = elementRegistry.get('Task1'),
connection = elementRegistry.get('SequenceFlow1'),
newWaypoints;
// when
modeling.removeShape(task);
newWaypoints = connection.waypoints.slice();
commandStack.undo();
commandStack.redo();
// then
expect(connection).to.have.waypoints(newWaypoints);
}));
});
describe('vertical connection layouting', function() {
var processDiagramXML = require('./RemoveElementBehavior.vertical.diagonal.bpmn');
beforeEach(bootstrapModeler(processDiagramXML, { modules: testModules }));
it('should execute', inject(function(modeling, elementRegistry) {
// given
var task = elementRegistry.get('Task1');
var sequenceFlow1 = elementRegistry.get('SequenceFlow1');
// when
modeling.removeShape(task);
// then
var waypoints = sequenceFlow1.waypoints;
// SequenceFlow2 should be deleted
expect(elementRegistry.get('SequenceFlow2')).to.be.undefined;
expect(elementRegistry.get(task.id)).to.be.undefined;
// source and target have one connection each
expect(elementRegistry.get('StartEvent1').outgoing.length).to.be.equal(1);
expect(elementRegistry.get('EndEvent1').incoming.length).to.be.equal(1);
// connection has Manhattan layout
expect(waypoints).to.have.length(4);
expect(waypoints[0].x).to.eql(waypoints[1].x);
expect(waypoints[1].y).to.eql(waypoints[2].y);
expect(waypoints[2].x).to.eql(waypoints[3].x);
}));
});
});
describe('do not combine sequence flows ', function() {
var processDiagramXML = require('./RemoveElementBehavior.bpmn');
beforeEach(bootstrapModeler(processDiagramXML, { modules: testModules }));
it('remove all if there are more than one incoming or outgoing', inject(function(modeling, elementRegistry) {
// given
var task = elementRegistry.get('Task3');
// when
modeling.removeShape(task);
// then
expect(elementRegistry.get(task.id)).to.be.undefined;
expect(elementRegistry.get('SequenceFlow4')).to.be.undefined;
expect(elementRegistry.get('SequenceFlow5')).to.be.undefined;
expect(elementRegistry.get('SequenceFlow6')).to.be.undefined;
expect(elementRegistry.get('StartEvent2').outgoing).to.be.empty;
expect(elementRegistry.get('StartEvent2').incoming).to.be.empty;
}));
it('when connection is not allowed', inject(function(modeling, elementRegistry) {
// given
var task = elementRegistry.get('Task2');
// when
modeling.removeShape(task);
// then
expect(elementRegistry.get(task.id)).to.be.undefined;
expect(elementRegistry.get('SequenceFlow3')).to.be.undefined;
expect(elementRegistry.get('DataOutputAssociation1')).to.be.undefined;
expect(elementRegistry.get('DataStoreReference1').incoming).to.be.empty;
expect(elementRegistry.get('IntermediateThrowEvent1').outgoing.length).to.be.eql(1);
}));
});
});
// helper //////////////////////
function point(p) {
return {
x: p.x,
y: p.y
};
}
================================================
FILE: test/spec/features/modeling/behavior/RemoveEmbeddedLabelBoundsBehavior.bpmn
================================================
================================================
FILE: test/spec/features/modeling/behavior/RemoveEmbeddedLabelBoundsBehaviorSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import coreModule from 'lib/core';
import modelingModule from 'lib/features/modeling';
import { getDi } from 'lib/util/ModelUtil';
describe('features/modeling - RemoveEmbeddedLabelBoundsBehavior', function() {
var processDiagramXML = require('./RemoveEmbeddedLabelBoundsBehavior.bpmn');
beforeEach(bootstrapModeler(processDiagramXML, {
modules: [
coreModule,
modelingModule
]
}));
var bounds,
di;
beforeEach(inject(function(elementRegistry, modeling) {
// given
var subProcess = elementRegistry.get('Activity_1');
di = getDi(subProcess);
bounds = di.get('label').get('bounds');
// when
modeling.resizeShape(subProcess, {
x: 0,
y: 0,
width: 100,
height: 100
});
}));
it('', function() {
// then
expect(di.get('label').get('bounds')).not.to.exist;
});
it('', inject(function(commandStack) {
// when
commandStack.undo();
// then
expect(di.get('label').get('bounds')).to.equal(bounds);
}));
it('', inject(function(commandStack) {
// when
commandStack.undo();
commandStack.redo();
// then
expect(di.get('label').get('bounds')).not.to.exist;
}));
});
================================================
FILE: test/spec/features/modeling/behavior/RemoveParticipantBehaviorSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import {
getDi,
is
} from 'lib/util/ModelUtil';
import modelingModule from 'lib/features/modeling';
import coreModule from 'lib/core';
describe('features/modeling - remove participant behavior', function() {
var testModules = [ coreModule, modelingModule ];
describe('when removing participant in collaboration', function() {
var processDiagramXML = require('../../../../fixtures/bpmn/collaboration/collaboration-message-flows.bpmn');
beforeEach(bootstrapModeler(processDiagramXML, { modules: testModules }));
describe('retain collaboration', function() {
it('execute', inject(function(modeling, elementRegistry, canvas) {
// given
var participantShape = elementRegistry.get('Participant_2');
// when
modeling.removeShape(participantShape);
// then
var rootElement = canvas.getRootElement();
expect(is(rootElement, 'bpmn:Collaboration')).to.be.ok;
}));
});
});
describe('when removing last remaining participant', function() {
var processDiagramXML = require('../../../../fixtures/bpmn/collaboration/collaboration-data-store.bpmn');
beforeEach(bootstrapModeler(processDiagramXML, { modules: testModules }));
describe('should transform diagram into process diagram', function() {
it('execute', inject(function(modeling, elementRegistry, canvas) {
// given
var participantShape = elementRegistry.get('_Participant_2'),
participant = participantShape.businessObject,
participantDi = getDi(participantShape),
process = participant.processRef,
collaborationElement = participantShape.parent,
collaboration = collaborationElement.businessObject,
diPlane = getDi(collaborationElement),
bpmnDefinitions = collaboration.$parent;
// when
modeling.removeShape(participantShape);
// then
expect(participant.$parent).not.to.be.ok;
var newRootShape = canvas.getRootElement(),
newRootBusinessObject = newRootShape.businessObject;
expect(newRootBusinessObject.$instanceOf('bpmn:Process')).to.be.true;
// collaboration DI is unwired
expect(participantDi.$parent).not.to.be.ok;
expect(getDi(collaborationElement)).not.to.be.ok;
expect(bpmnDefinitions.rootElements).not.to.include(process);
expect(bpmnDefinitions.rootElements).not.to.include(collaboration);
// process DI is wired
expect(diPlane.bpmnElement).to.eql(newRootBusinessObject);
expect(getDi(newRootShape)).to.eql(diPlane);
expect(bpmnDefinitions.rootElements).to.include(newRootBusinessObject);
// data store is preserved
expect(newRootShape.children).to.have.length(1);
}));
it('undo', inject(function(modeling, elementRegistry, canvas, commandStack) {
// given
var participantShape = elementRegistry.get('_Participant_2'),
participant = participantShape.businessObject,
originalRootElement = participantShape.parent,
originalRootElementBo = originalRootElement.businessObject,
originalRootElementDi = getDi(originalRootElement),
bpmnDefinitions = originalRootElementBo.$parent,
participantDi = getDi(participantShape),
diPlane = participantDi.$parent;
modeling.removeShape(participantShape);
// when
commandStack.undo();
// then
expect(participant.$parent).to.eql(originalRootElementBo);
expect(originalRootElementBo.$parent).to.eql(bpmnDefinitions);
expect(canvas.getRootElement()).to.eql(originalRootElement);
// di is unwired
expect(participantDi.$parent).to.eql(originalRootElementDi);
// new di is wired
expect(diPlane.bpmnElement).to.eql(originalRootElementBo);
}));
});
});
describe('when removing all diagram content', function() {
var processDiagramXML = require('../../../../fixtures/bpmn/collaboration/collaboration-data-store.bpmn');
beforeEach(bootstrapModeler(processDiagramXML, { modules: testModules }));
describe('should transform diagram into process diagram', function() {
it('execute', inject(function(modeling, elementRegistry, canvas) {
// given
var participantShape = elementRegistry.get('_Participant_2'),
participant = participantShape.businessObject,
participantDi = getDi(participantShape),
process = participant.processRef,
collaborationElement = participantShape.parent,
collaboration = collaborationElement.businessObject,
diPlane = getDi(collaborationElement),
bpmnDefinitions = collaboration.$parent;
// when
var rootElement = canvas.getRootElement();
var elements = elementRegistry.filter(function(element) {
return element !== rootElement;
});
modeling.removeElements(elements);
// then
expect(participant.$parent).not.to.be.ok;
var newRootShape = canvas.getRootElement(),
newRootBusinessObject = newRootShape.businessObject;
expect(newRootBusinessObject.$instanceOf('bpmn:Process')).to.be.true;
// collaboration DI is unwired
expect(participantDi.$parent).not.to.be.ok;
expect(getDi(collaborationElement)).not.to.be.ok;
expect(bpmnDefinitions.rootElements).not.to.include(process);
expect(bpmnDefinitions.rootElements).not.to.include(collaboration);
// process DI is wired
expect(diPlane.bpmnElement).to.eql(newRootBusinessObject);
expect(getDi(newRootShape)).to.eql(diPlane);
expect(bpmnDefinitions.rootElements).to.include(newRootBusinessObject);
}));
it('undo', inject(function(modeling, elementRegistry, canvas, commandStack) {
// given
var participantShape = elementRegistry.get('_Participant_2'),
participant = participantShape.businessObject,
originalRootElement = participantShape.parent,
originalRootElementBo = originalRootElement.businessObject,
originalRootElementDi = getDi(originalRootElement),
bpmnDefinitions = originalRootElementBo.$parent,
participantDi = getDi(participantShape),
diPlane = participantDi.$parent;
var rootElement = canvas.getRootElement();
var elements = elementRegistry.filter(function(element) {
return element !== rootElement;
});
modeling.removeElements(elements);
// when
commandStack.undo();
// then
expect(participant.$parent).to.eql(originalRootElementBo);
expect(originalRootElementBo.$parent).to.eql(bpmnDefinitions);
expect(canvas.getRootElement()).to.eql(originalRootElement);
// di is unwired
expect(participantDi.$parent).to.eql(originalRootElementDi);
// new di is wired
expect(diPlane.bpmnElement).to.eql(originalRootElementBo);
}));
});
});
});
================================================
FILE: test/spec/features/modeling/behavior/ReplaceConnectionBehavior.association.bpmn
================================================
DataObjectReference
================================================
FILE: test/spec/features/modeling/behavior/ReplaceConnectionBehavior.boundary-events.bpmn
================================================
SequenceFlow_1
SequenceFlow_1
================================================
FILE: test/spec/features/modeling/behavior/ReplaceConnectionBehavior.message-sequence-flow.bpmn
================================================
SequenceFlow_1
SequenceFlow_2
SequenceFlow_2
SequenceFlow_3
SequenceFlow_1
SequenceFlow_3
Flow_0ypde12
Flow_0ypde12
================================================
FILE: test/spec/features/modeling/behavior/ReplaceConnectionBehaviorSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import {
is
} from 'lib/util/ModelUtil';
import {
find
} from 'min-dash';
import {
getMid
} from 'diagram-js/lib/layout/LayoutUtil';
import modelingModule from 'lib/features/modeling';
import moveModule from 'diagram-js/lib/features/move';
import coreModule from 'lib/core';
import bendpointsModule from 'diagram-js/lib/features/bendpoints';
import {
createCanvasEvent as canvasEvent
} from '../../../../util/MockEvents';
function getConnection(source, target, connectionOrType) {
return find(source.outgoing, function(c) {
return c.target === target &&
(typeof connectionOrType === 'string' ? is(c, connectionOrType) : c === connectionOrType);
});
}
function expectConnected(source, target, connectionOrType, attrs) {
var connection = getConnection(source, target, connectionOrType);
expect(connection).to.exist;
if (attrs) {
Object.keys(attrs).forEach(function(key) {
expect(connection.businessObject[key]).to.eql(attrs[key]);
});
}
}
function expectNotConnected(source, target, connectionOrType) {
expect(getConnection(source, target, connectionOrType)).not.to.exist;
}
describe('features/modeling - replace connection', function() {
var testModules = [
coreModule,
moveModule,
modelingModule
];
describe('should replace SequenceFlow <> MessageFlow', function() {
var processDiagramXML = require('./ReplaceConnectionBehavior.message-sequence-flow.bpmn');
beforeEach(bootstrapModeler(processDiagramXML, {
modules: testModules
}));
var element;
beforeEach(inject(function(elementRegistry) {
element = function(id) {
return elementRegistry.get(id);
};
}));
describe('after reconnecting', function() {
it('sequence flow from a participant', inject(function(modeling) {
// given
var participant2 = element('Participant_2'),
subProcess1 = element('SubProcess_1'),
connection = element('SequenceFlow_1');
var newWaypoints = [
{ x: participant2.x + 200, y: participant2.y },
{ x: subProcess1.x, y: subProcess1.y + 50 }
];
// when
modeling.reconnect(connection, participant2, connection.target, newWaypoints);
// then
expectConnected(participant2, subProcess1, 'bpmn:MessageFlow');
}));
});
describe('after reconnecting start', function() {
it('sequence flow from a participant', inject(function(modeling) {
// given
var participant2 = element('Participant_2'),
subProcess1 = element('SubProcess_1'),
connection = element('SequenceFlow_1');
var newWaypoints = [
{ x: participant2.x + 200, y: participant2.y },
{ x: subProcess1.x, y: subProcess1.y + 50 }
];
// when
modeling.reconnectStart(connection, participant2, newWaypoints);
// then
expectConnected(participant2, subProcess1, 'bpmn:MessageFlow');
}));
});
describe('after reconnecting end', function() {
it('sequence flow to another task', inject(function(modeling) {
// given
var task4Shape = element('Task_4');
var connection = element('SequenceFlow_1');
var newWaypoints = [ connection.waypoints[0], { x: task4Shape.x + 30, y: task4Shape.y } ];
// when
modeling.reconnectEnd(connection, task4Shape, newWaypoints);
// then
expectConnected(element('Task_2'), task4Shape, 'bpmn:MessageFlow');
}));
it('message flow to another task', inject(function(elementRegistry, modeling) {
// given
var task4Shape = element('Task_4');
var connection = element('MessageFlow_1');
var newWaypoints = [ connection.waypoints[0], { x: task4Shape.x, y: task4Shape.y + 20 } ];
// when
modeling.reconnectEnd(connection, task4Shape, newWaypoints);
// then
expectConnected(element('Task_3'), task4Shape, 'bpmn:SequenceFlow');
}));
it('sequence flow to a participant', inject(function(elementRegistry, modeling) {
// given
var participant2 = element('Participant_2');
var connection = element('SequenceFlow_1');
var newWaypoints = [ connection.waypoints[0], { x: participant2.x, y: participant2.y } ];
// when
modeling.reconnectEnd(connection, participant2, newWaypoints);
// then
expectConnected(element('Task_2'), participant2, 'bpmn:MessageFlow');
}));
});
describe('moving single shape', function() {
it('execute', inject(function(modeling, elementRegistry) {
// given
var taskShape = element('Task_2'),
targetShape = element('Participant_2');
// when
modeling.moveElements([ taskShape ], { x: 0, y: 330 }, targetShape);
// then
expect(taskShape.parent).to.eql(targetShape);
expectNotConnected(element('StartEvent_1'), taskShape, 'bpmn:SequenceFlow');
expectConnected(taskShape, element('Task_4'), 'bpmn:SequenceFlow');
expectConnected(taskShape, element('SubProcess_1'), 'bpmn:MessageFlow');
}));
it('undo', inject(function(modeling, elementRegistry, commandStack) {
// given
var taskShape = element('Task_2'),
targetShape = element('Participant_2'),
oldParent = taskShape.parent;
modeling.moveElements([ taskShape ], { x: 0, y: 300 }, targetShape);
// when
commandStack.undo();
// then
expect(taskShape.parent).to.eql(oldParent);
expectConnected(element('StartEvent_1'), taskShape, element('SequenceFlow_3'));
expectConnected(taskShape, element('Task_4'), element('MessageFlow_5'));
expectConnected(taskShape, element('SubProcess_1'), element('SequenceFlow_1'));
}));
});
describe('moving multiple shapes', function() {
it('execute', inject(function(modeling, elementRegistry) {
// given
var startEventShape = element('StartEvent_1'),
taskShape = element('Task_2'),
targetShape = element('Participant_2');
// when
modeling.moveElements([ startEventShape, taskShape ], { x: 0, y: 330 }, targetShape);
// then
expect(taskShape.parent).to.eql(targetShape);
expect(startEventShape.parent).to.eql(targetShape);
expectConnected(startEventShape, taskShape, element('SequenceFlow_3'));
expectNotConnected(element('Participant_2'), startEventShape, 'bpmn:MessageFlow');
expectConnected(taskShape, element('SubProcess_1'), 'bpmn:MessageFlow');
}));
it('undo', inject(function(modeling, elementRegistry, commandStack) {
// given
var taskShape = element('Task_2'),
targetShape = element('Participant_2'),
oldParent = taskShape.parent;
modeling.moveElements([ taskShape ], { x: 0, y: 300 }, targetShape);
// when
commandStack.undo();
// then
expect(taskShape.parent).to.eql(oldParent);
expectConnected(element('StartEvent_1'), taskShape, element('SequenceFlow_3'));
expectConnected(taskShape, element('Task_4'), element('MessageFlow_5'));
expectConnected(taskShape, element('SubProcess_1'), element('SequenceFlow_1'));
expectConnected(element('Participant_2'), element('StartEvent_1'), element('MessageFlow_4'));
}));
});
describe('moving nested shapes', function() {
it('execute', inject(function(modeling, elementRegistry) {
// given
var subProcessShape = element('SubProcess_1'),
targetShape = element('Participant_2');
// when
modeling.moveElements([ subProcessShape ], { x: 0, y: 530 }, targetShape);
// then
expect(subProcessShape.parent).to.eql(targetShape);
expectConnected(element('Task_2'), subProcessShape, 'bpmn:MessageFlow');
expectNotConnected(element('Task_1'), element('Participant_2'), 'bpmn:MessageFlow');
}));
it('undo', inject(function(modeling, elementRegistry, commandStack) {
// given
var subProcessShape = element('SubProcess_1'),
targetShape = element('Participant_2');
modeling.moveElements([ subProcessShape ], { x: 0, y: 530 }, targetShape);
// when
commandStack.undo();
// then
expectConnected(element('Task_2'), subProcessShape, element('SequenceFlow_1'));
expectConnected(element('Task_1'), element('Participant_2'), element('MessageFlow_3'));
}));
});
});
describe('should replace SequenceFlow <> Association', function() {
var processDiagramXML = require('./ReplaceConnectionBehavior.message-sequence-flow.bpmn');
beforeEach(bootstrapModeler(processDiagramXML, {
modules: testModules
}));
var element;
beforeEach(inject(function(elementRegistry) {
element = function(id) {
return elementRegistry.get(id);
};
}));
it('after replacing', inject(function(bpmnReplace) {
// given
var boundary = element('BoundaryEvent');
// when
bpmnReplace.replaceElement(boundary, { type: 'bpmn:BoundaryEvent', eventDefinitionType: 'bpmn:CompensateEventDefinition' });
// then
expectConnected(element('BoundaryEvent'), element('Task_5'), 'bpmn:Association', { associationDirection: 'One' });
}));
});
describe('text/data association', function() {
var processDiagramXML = require('./ReplaceConnectionBehavior.association.bpmn');
beforeEach(bootstrapModeler(processDiagramXML, {
modules: testModules
}));
var element;
beforeEach(inject(function(elementRegistry) {
element = function(id) {
return elementRegistry.get(id);
};
}));
describe('reconnecting data output association to text annotation', function() {
it('execute', inject(function(modeling) {
// given
var textAnnotation = element('TextAnnotation_1'),
dataObjectReference = element('DataObjectReference'),
dataOutputAssociation = element('DataOutputAssociation');
// when
modeling.reconnectStart(dataOutputAssociation, textAnnotation, { x: 708, y: 100 });
// then
expectConnected(textAnnotation, dataObjectReference, 'bpmn:Association');
}));
it('undo', inject(function(modeling, commandStack) {
// given
var task = element('Task'),
dataObjectReference = element('DataObjectReference'),
textAnnotation = element('TextAnnotation_1'),
dataOutputAssociation = element('DataOutputAssociation');
modeling.reconnectStart(dataOutputAssociation, textAnnotation, { x: 708, y: 100 });
// when
commandStack.undo();
// then
expectConnected(task, dataObjectReference, dataOutputAssociation);
}));
});
});
describe('boundary events', function() {
describe('moving host', function() {
var processDiagramXML = require('./ReplaceConnectionBehavior.boundary-events.bpmn');
beforeEach(bootstrapModeler(processDiagramXML, {
modules: testModules
}));
var element;
beforeEach(inject(function(elementRegistry) {
element = function(id) {
return elementRegistry.get(id);
};
}));
it('should remove invalid connections', inject(function(modeling) {
// given
var taskShape = element('Task_1'),
taskShape2 = element('Task_2'),
targetShape = element('SubProcess_1'),
boundaryEvent = element('BoundaryEvent_1'),
sequenceFlow = element('SequenceFlow_1');
// when
modeling.moveElements([ taskShape ], { x: 30, y: 200 }, targetShape);
// then
expectNotConnected(boundaryEvent, taskShape2 ,sequenceFlow);
}));
it('should remove invalid connections (undo)', inject(function(modeling, commandStack) {
// given
var taskShape = element('Task_1'),
taskShape2 = element('Task_2'),
boundaryEvent = element('BoundaryEvent_1'),
targetShape = element('SubProcess_1'),
sequenceFlow = element('SequenceFlow_1');
// when
modeling.moveElements([ taskShape ], { x: 30, y: 200 }, targetShape);
commandStack.undo();
// then
expectConnected(boundaryEvent, taskShape2, sequenceFlow);
}));
it('should remove invalid connections (redo)', inject(function(modeling, commandStack) {
// given
var taskShape = element('Task_1'),
targetShape = element('SubProcess_1'),
sequenceFlow = element('SequenceFlow_1');
// when
modeling.moveElements([ taskShape ], { x: 30, y: 200 }, targetShape);
commandStack.undo();
commandStack.redo();
// then
expectNotConnected(taskShape, targetShape, sequenceFlow);
}));
});
describe('moving along host with outgoing', function() {
var processDiagramXML = require('../../../../fixtures/bpmn/features/replace/connection.bpmn');
beforeEach(bootstrapModeler(processDiagramXML, {
modules: testModules
}));
it('should move connections outgoing from an attacher',
inject(function(canvas, elementFactory, move, dragging, elementRegistry, selection) {
// given
var rootShape = canvas.getRootElement(),
rootGfx = elementRegistry.getGraphics(rootShape),
host = elementRegistry.get('Task_1'),
task = elementRegistry.get('Task_2'),
boundaryEvent = elementRegistry.get('BoundaryEvent_1'),
connection = elementRegistry.get('SequenceFlow_1');
selection.select([ host, boundaryEvent, task ]);
// when
move.start(canvasEvent({ x: 0, y: 0 }), host);
dragging.hover({
element: rootShape,
gfx: rootGfx
});
dragging.move(canvasEvent({ x: 0, y: 300 }));
dragging.end();
// then
expect(connection.waypoints[0].x).to.equal(165);
expect(connection.waypoints[0].y).to.equal(477);
expect(connection.waypoints[1].x).to.equal(165);
expect(connection.waypoints[1].y).to.equal(544);
expect(connection.waypoints[2].x).to.equal(234);
expect(connection.waypoints[2].y).to.equal(544);
expect(connection.source).to.eql(boundaryEvent);
expect(connection.target).to.eql(task);
})
);
});
describe('dragging selection cleanup', function() {
var processDiagramXML = require('./ReplaceConnectionBehavior.message-sequence-flow.bpmn');
beforeEach(bootstrapModeler(processDiagramXML, {
modules: testModules.concat(bendpointsModule)
}));
it('should select the new connection if replaced one was selected before',
inject(function(bendpointMove, dragging, elementRegistry, selection) {
// given
var participant2 = elementRegistry.get('Participant_2'),
connection = elementRegistry.get('SequenceFlow_1');
selection.select([ connection ]);
// when
bendpointMove.start(canvasEvent(connection.waypoints[0]), connection, 0);
dragging.hover({
element: participant2
});
dragging.move(canvasEvent({ x: participant2.x + 200, y: participant2.y }));
dragging.end();
// then
expect(selection.get()).to.deep.eql(participant2.outgoing.slice(-1));
})
);
it('should not interfere with connection to other element',
inject(function(bendpointMove, dragging, elementRegistry, selection) {
// given
var participant2 = elementRegistry.get('Participant_2'),
connection = elementRegistry.get('SequenceFlow_1');
selection.select([ participant2 ]);
// when
bendpointMove.start(canvasEvent(connection.waypoints[0]), connection, 0);
dragging.hover({
element: participant2
});
dragging.move(canvasEvent({ x: participant2.x + 200, y: participant2.y }));
dragging.end();
// then
expect(selection.get()).to.deep.eql([ participant2 ]);
})
);
});
});
describe('reconnecting to create loops', function() {
var processDiagramXML = require('./ReplaceConnectionBehavior.message-sequence-flow.bpmn');
beforeEach(bootstrapModeler(processDiagramXML, {
modules: testModules
}));
it('should set correct parents when reconnecting message flow start to task',
inject(function(elementRegistry, modeling) {
// given
var task = elementRegistry.get('Task_4'),
connection = elementRegistry.get('MessageFlow_5');
// when
modeling.reconnectStart(connection, task, getMid(task));
// then
expect(connection.parent).to.not.exist;
expect(task.outgoing[0]).to.exist;
expect(task.outgoing[0]).to.have.property('parent', task.parent);
})
);
it('should set correct parents when reconnecting message flow end to task',
inject(function(elementRegistry, modeling) {
// given
var task = elementRegistry.get('Task_3'),
connection = elementRegistry.get('MessageFlow_1');
// when
modeling.reconnectEnd(connection, task, getMid(task));
// then
expect(connection.parent).to.not.exist;
expect(task.outgoing[0]).to.exist;
expect(task.outgoing[0]).to.have.property('parent', task.parent);
})
);
it('should set correct parents when reconnecting message flow from participant to task',
inject(function(elementRegistry, modeling) {
// given
var task = elementRegistry.get('Task_3'),
connection = elementRegistry.get('MessageFlow_6');
// when
modeling.reconnectStart(connection, task, getMid(task));
// then
expect(connection.parent).to.not.exist;
expect(task.outgoing[1]).to.exist;
expect(task.outgoing[1]).to.have.property('parent', task.parent);
})
);
});
});
================================================
FILE: test/spec/features/modeling/behavior/ReplaceElementBehaviourSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import replacePreviewModule from 'lib/features/replace-preview';
import modelingModule from 'lib/features/modeling';
import moveModule from 'diagram-js/lib/features/move';
import coreModule from 'lib/core';
import copyPasteModule from 'lib/features/copy-paste';
import {
getBusinessObject,
is
} from 'lib/util/ModelUtil';
import {
createCanvasEvent as canvasEvent
} from '../../../../util/MockEvents';
import {
query as domQuery
} from 'min-dom';
var ATTACH = { attach: true };
describe('features/modeling - replace element behavior', function() {
describe('', function() {
var testModules = [
replacePreviewModule,
modelingModule,
coreModule,
moveModule,
copyPasteModule
];
describe('Start Events', function() {
var diagramXML = require('../../../../fixtures/bpmn/event-sub-processes.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
var moveShape;
beforeEach(inject(function(move, dragging, elementRegistry) {
moveShape = function(shape, target, position) {
var startPosition = { x: shape.x + 10 + shape.width / 2, y: shape.y + 30 + shape.height / 2 };
move.start(canvasEvent(startPosition), shape);
dragging.hover({
element: target,
gfx: elementRegistry.getGraphics(target)
});
dragging.move(canvasEvent(position));
};
}));
it('should select the replacement after replacing the start event',
inject(function(elementRegistry, canvas, dragging, move, selection) {
// given
var startEvent = elementRegistry.get('StartEvent_1'),
rootElement = canvas.getRootElement();
// when
moveShape(startEvent, rootElement, { x: 140, y: 250 });
dragging.end();
var replacement = elementRegistry.get('StartEvent_1');
// then
expect(selection.get()).to.include(replacement);
expect(selection.get()).not.to.include(startEvent);
})
);
it('should select all moved shapes after some of them got replaced',
inject(function(elementRegistry, canvas, dragging, move, selection) {
// given
var startEvent1 = elementRegistry.get('StartEvent_1'),
startEvent2 = elementRegistry.get('StartEvent_2'),
startEvent3 = elementRegistry.get('StartEvent_3'),
rootElement = canvas.getRootElement();
// when
selection.select([ startEvent1, startEvent2, startEvent3 ]);
moveShape(startEvent1, rootElement, { x: 140, y: 250 });
dragging.end();
var replacements = elementRegistry.filter(function(element) {
if (is(element, 'bpmn:StartEvent') && element.type !== 'label') {
return true;
}
});
// then
expect(selection.get()).to.include(replacements[0]);
expect(selection.get()).to.include(replacements[1]);
expect(selection.get()).to.include(replacements[2]);
})
);
it('should not replace non-interrupting start event after copy paste',
inject(function(canvas, copyPaste, elementRegistry) {
// given
var subProcess = elementRegistry.get('SubProcess_1'),
rootElement = canvas.getRootElement();
// when
copyPaste.copy(subProcess);
var elements = copyPaste.paste({
element: rootElement,
point: {
x: 100,
y: 100
}
});
// then
var startEvents = elements.filter(function(element) {
if (is(element, 'bpmn:StartEvent') && getBusinessObject(element).get('eventDefinitions').length) {
return true;
}
});
startEvents.forEach(function(startEvent) {
expect(getBusinessObject(startEvent).get('isInterrupting')).to.be.false;
});
})
);
});
describe('Cancel Events', function() {
var diagramXML = require('../../../../fixtures/bpmn/features/replace/cancel-events.bpmn');
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
describe('normal', function() {
it('should unclaim ID and claim same ID with new element',
inject(function(elementRegistry, bpmnReplace) {
// given
var transaction = elementRegistry.get('Transaction_1');
var ids = transaction.businessObject.$model.ids;
// when
var subProcess = bpmnReplace.replaceElement(transaction, { type: 'bpmn:SubProcess' });
// then
expect(ids.assigned(transaction.id)).to.eql(subProcess.businessObject);
expect(ids.assigned(subProcess.id)).to.eql(subProcess.businessObject);
expect(subProcess.id).to.eql(transaction.id);
})
);
it('should REVERT unclaim ID and claim same ID with new element on UNDO',
inject(function(elementRegistry, bpmnReplace, commandStack) {
// given
var transaction = elementRegistry.get('Transaction_1');
var ids = transaction.businessObject.$model.ids;
var subProcess = bpmnReplace.replaceElement(transaction, { type: 'bpmn:SubProcess' });
// when
commandStack.undo();
// then
expect(ids.assigned(transaction.id)).to.eql(transaction.businessObject);
expect(subProcess.id).not.to.equal(transaction.id);
})
);
it('should replace CancelEvent when morphing transaction',
inject(function(elementRegistry, bpmnReplace) {
// given
var transaction = elementRegistry.get('Transaction_1'),
endEvent = elementRegistry.get('EndEvent_1');
// when
bpmnReplace.replaceElement(endEvent, {
type: 'bpmn:EndEvent',
eventDefinitionType: 'bpmn:CancelEventDefinition'
});
var subProcess = bpmnReplace.replaceElement(transaction, { type: 'bpmn:SubProcess' });
var newEndEvent = subProcess.children[0].businessObject;
// then
expect(subProcess.children).to.have.length(1);
expect(newEndEvent.eventDefinitionTypes).not.to.exist;
})
);
it('should replace CancelEvent when morphing transaction -> undo',
inject(function(elementRegistry, bpmnReplace, commandStack) {
// given
var transaction = elementRegistry.get('Transaction_1'),
endEvent = elementRegistry.get('EndEvent_1');
// when
bpmnReplace.replaceElement(endEvent, {
type: 'bpmn:EndEvent',
eventDefinitionType: 'bpmn:CancelEventDefinition'
});
bpmnReplace.replaceElement(transaction, { type: 'bpmn:SubProcess' });
commandStack.undo();
var endEventAfter = elementRegistry.filter(function(element) {
return (element.id !== 'EndEvent_2' && element.type === 'bpmn:EndEvent');
})[0];
// then
expect(transaction.children).to.have.length(1);
expect(endEventAfter.businessObject.get('eventDefinitions')).not.to.be.empty;
})
);
it('should replace a CancelEvent when moving outside of a transaction',
inject(function(elementRegistry, bpmnReplace, modeling) {
// given
var process = elementRegistry.get('Process_1'),
transaction = elementRegistry.get('Transaction_1'),
endEvent = elementRegistry.get('EndEvent_1');
// when
var cancelEvent = bpmnReplace.replaceElement(endEvent, {
type: 'bpmn:EndEvent',
eventDefinitionType: 'bpmn:CancelEventDefinition'
});
modeling.moveElements([ cancelEvent ], { x: 0, y: 150 }, process);
var endEventAfter = elementRegistry.filter(function(element) {
return (element.parent === process && element.type === 'bpmn:EndEvent');
})[0];
// then
expect(transaction.children).to.have.length(0);
expect(endEventAfter.businessObject.get('eventDefinitions')).to.be.empty;
})
);
it('should replace a CancelEvent when moving outside of a transaction -> undo',
inject(function(elementRegistry, bpmnReplace, modeling, commandStack) {
// given
var process = elementRegistry.get('Process_1'),
transaction = elementRegistry.get('Transaction_1'),
endEvent = elementRegistry.get('EndEvent_1');
// when
var cancelEvent = bpmnReplace.replaceElement(endEvent, {
type: 'bpmn:EndEvent',
eventDefinitionType: 'bpmn:CancelEventDefinition'
});
modeling.moveElements([ cancelEvent ], { x: 0, y: 150 }, process);
commandStack.undo();
var endEventAfter = elementRegistry.filter(function(element) {
return (element.id !== 'EndEvent_2' && element.type === 'bpmn:EndEvent');
})[0];
// then
expect(transaction.children).to.have.length(1);
expect(endEventAfter.businessObject.get('eventDefinitions')).not.to.be.empty;
})
);
});
describe('boundary events', function() {
it('should replace CancelBoundaryEvent when morphing from a transaction',
inject(function(elementRegistry, bpmnReplace) {
// given
var boundaryEvent = elementRegistry.get('BoundaryEvent_1'),
transaction = elementRegistry.get('Transaction_1');
// when
bpmnReplace.replaceElement(boundaryEvent, {
type: 'bpmn:BoundaryEvent',
eventDefinitionType: 'bpmn:CancelEventDefinition'
});
var subProcess = bpmnReplace.replaceElement(transaction, { type: 'bpmn:SubProcess' });
var newBoundaryEvent = subProcess.attachers[0].businessObject;
// then
expect(newBoundaryEvent.eventDefinitionTypes).not.to.exist;
expect(newBoundaryEvent.attachedToRef).to.equal(subProcess.businessObject);
expect(elementRegistry.get('Transaction_1')).to.eql(subProcess);
})
);
it('should replace CancelBoundaryEvent when morphing from a transaction -> undo',
inject(function(elementRegistry, bpmnReplace, commandStack) {
// given
var boundaryEvent = elementRegistry.get('BoundaryEvent_1'),
transaction = elementRegistry.get('Transaction_1');
// when
bpmnReplace.replaceElement(boundaryEvent, {
type: 'bpmn:BoundaryEvent',
eventDefinitionType: 'bpmn:CancelEventDefinition'
});
bpmnReplace.replaceElement(transaction, { type: 'bpmn:SubProcess' });
commandStack.undo();
var afterBoundaryEvent = elementRegistry.filter(function(element) {
return (element.type === 'bpmn:BoundaryEvent' && element.id === 'BoundaryEvent_1');
})[0];
// then
expect(afterBoundaryEvent.businessObject.get('eventDefinitions')).not.to.be.empty;
expect(afterBoundaryEvent.businessObject.attachedToRef).to.equal(transaction.businessObject);
expect(transaction.attachers).to.have.length(1);
})
);
it('should replace CancelBoundaryEvent when attaching to a NON-transaction',
inject(function(elementRegistry, bpmnReplace, modeling) {
// given
var boundaryEvent = elementRegistry.get('BoundaryEvent_1'),
subProcess = elementRegistry.get('SubProcess_1'),
process = elementRegistry.get('Process_1'),
transaction = elementRegistry.get('Transaction_1');
// when
var newBoundaryEvent = bpmnReplace.replaceElement(boundaryEvent, {
type: 'bpmn:BoundaryEvent',
eventDefinitionType: 'bpmn:CancelEventDefinition'
});
modeling.moveElements([ newBoundaryEvent ], { x: 500, y: 0 }, subProcess, ATTACH);
var movedBoundaryEvent = elementRegistry.filter(function(element) {
return (element.type === 'bpmn:BoundaryEvent' && element.id === 'BoundaryEvent_1');
})[0];
// then
expect(movedBoundaryEvent.businessObject.get('eventDefinitions')).to.be.empty;
expect(movedBoundaryEvent.businessObject.attachedToRef).to.equal(subProcess.businessObject);
expect(movedBoundaryEvent.parent).to.equal(process);
expect(movedBoundaryEvent.host).to.equal(subProcess);
expect(subProcess.attachers).to.contain(movedBoundaryEvent);
expect(transaction.attachers).to.be.empty;
})
);
it('should replace CancelBoundaryEvent when attaching to a NON-transaction -> undo',
inject(function(elementRegistry, bpmnReplace, modeling, commandStack) {
// given
var boundaryEvent = elementRegistry.get('BoundaryEvent_1'),
transaction = elementRegistry.get('Transaction_1'),
subProcess = elementRegistry.get('SubProcess_1');
// when
var newBoundaryEvent = bpmnReplace.replaceElement(boundaryEvent, {
type: 'bpmn:BoundaryEvent',
eventDefinitionType: 'bpmn:CancelEventDefinition'
});
modeling.moveElements([ newBoundaryEvent ], { x: 500, y: 0 }, subProcess, ATTACH);
commandStack.undo();
var movedBoundaryEvent = elementRegistry.filter(function(element) {
return (element.type === 'bpmn:BoundaryEvent' && element.id === 'BoundaryEvent_1');
})[0];
// then
expect(movedBoundaryEvent.businessObject.get('eventDefinitions')).not.to.be.empty;
expect(movedBoundaryEvent.businessObject.attachedToRef).to.equal(transaction.businessObject);
expect(movedBoundaryEvent.host).to.equal(transaction);
expect(transaction.attachers).to.contain(movedBoundaryEvent);
expect(subProcess.attachers).to.have.length(1);
})
);
it('should NOT allow morphing into an IntermediateEvent',
inject(function(elementRegistry, bpmnReplace, commandStack, move, dragging) {
// given
var boundaryEvent = elementRegistry.get('BoundaryEvent_1'),
subProcess = elementRegistry.get('SubProcess_1');
// when
var newBoundaryEvent = bpmnReplace.replaceElement(boundaryEvent, {
type: 'bpmn:BoundaryEvent',
eventDefinitionType: 'bpmn:CancelEventDefinition'
});
move.start(canvasEvent({ x: 0, y: 0 }), newBoundaryEvent);
dragging.hover({
gfx: elementRegistry.getGraphics(subProcess),
element: subProcess
});
dragging.move(canvasEvent({ x: 450, y: -50 }));
var canExecute = dragging.context().data.context.canExecute;
// then
expect(canExecute).to.be.false;
})
);
});
});
describe('outline', function() {
var diagramXML = require('../../../../fixtures/bpmn/features/replace/connection.bpmn');
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
it('should update size of outline on replace', inject(function(bpmnReplace, elementRegistry) {
// given
var task = elementRegistry.get('Task_2');
// when
var subProcess = bpmnReplace.replaceElement(task, {
type: 'bpmn:SubProcess',
isExpanded: true
});
// then
var gfx = elementRegistry.getGraphics(subProcess);
var outline = domQuery('.djs-outline', gfx);
expect(outline.getBBox().width).to.equal(gfx.getBBox().width);
expect(outline.getBBox().height).to.equal(gfx.getBBox().height);
}));
});
});
describe('shape.create', function() {
var diagramXML = require('../../../../fixtures/bpmn/event-sub-processes.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: [
replacePreviewModule,
modelingModule,
coreModule
]
}));
it('should replace Timer Start Event with None Start Event when created in SubProcess',
inject(function(elementRegistry, modeling, elementFactory) {
// given
var subProcess = elementRegistry.get('SubProcess_3'),
startEvent = elementFactory.createShape(
{ type: 'bpmn:StartEvent', eventDefinitionType: 'bpmn:TimerEventDefinition' }),
id = startEvent.businessObject.id;
// when
modeling.createElements(
startEvent, { x: subProcess.x + 30, y: subProcess.y + 30 }, subProcess);
// then
var createdEvent = elementRegistry.get(id);
expect(createdEvent).to.exist;
expect(createdEvent.businessObject.get('eventDefinitions')).to.be.empty;
})
);
it('should NOT replace Timer Start Event when created in EventSubProcess',
inject(function(elementRegistry, modeling, elementFactory) {
// given
var subProcess = elementRegistry.get('SubProcess_2'),
startEvent = elementFactory.createShape(
{ type: 'bpmn:StartEvent', eventDefinitionType: 'bpmn:TimerEventDefinition' }),
id = startEvent.businessObject.id;
// when
modeling.createElements(
startEvent, { x: subProcess.x + 30, y: subProcess.y + 30 }, subProcess);
// then
var createdEvent = elementRegistry.get(id);
expect(createdEvent).to.eql(startEvent);
expect(createdEvent.businessObject.get('eventDefinitions')).to.have.lengthOf(1);
expect(
is(createdEvent.businessObject.get('eventDefinitions')[0], 'bpmn:TimerEventDefinition')
).to.be.true;
})
);
it('should replace Non-Interrupting Start Event when created in Process',
inject(function(elementRegistry, modeling, elementFactory) {
// given
var processElement = elementRegistry.get('Process_1'),
startEvent = elementFactory.createShape({
type: 'bpmn:StartEvent', eventDefinitionType: 'bpmn:TimerEventDefinition',
isInterrupting: false
}),
id = startEvent.businessObject.id;
// when
modeling.createElements(
startEvent, { x: 30, y: 30 }, processElement);
// then
var createdEvent = elementRegistry.get(id);
expect(createdEvent).to.exist;
expect(createdEvent.businessObject.get('eventDefinitions')).to.be.empty;
expect(createdEvent.businessObject.get('isInterrupting')).to.be.true;
})
);
});
});
================================================
FILE: test/spec/features/modeling/behavior/ResizeBehavior.lanes.bpmn
================================================
================================================
FILE: test/spec/features/modeling/behavior/ResizeBehavior.lanes.vertical.bpmn
================================================
================================================
FILE: test/spec/features/modeling/behavior/ResizeBehavior.participant.bpmn
================================================
================================================
FILE: test/spec/features/modeling/behavior/ResizeBehavior.participant.vertical.bpmn
================================================
================================================
FILE: test/spec/features/modeling/behavior/ResizeBehavior.subProcess.bpmn
================================================
================================================
FILE: test/spec/features/modeling/behavior/ResizeBehavior.textAnnotation.bpmn
================================================
================================================
FILE: test/spec/features/modeling/behavior/ResizeBehavior.utility.lanes-flowNodes.bpmn
================================================
Boundary
Task_Boundary
Task
SequenceFlow_From_Boundary
SequenceFlow
SequenceFlow_From_Boundary
SequenceFlow
================================================
FILE: test/spec/features/modeling/behavior/ResizeBehavior.utility.lanes.bpmn
================================================
================================================
FILE: test/spec/features/modeling/behavior/ResizeBehavior.utility.lanes.vertical-flowNodes.bpmn
================================================
Boundary
Task_Boundary
Task
SequenceFlow_From_Boundary
SequenceFlow
SequenceFlow_From_Boundary
SequenceFlow
================================================
FILE: test/spec/features/modeling/behavior/ResizeBehavior.utility.lanes.vertical.bpmn
================================================
================================================
FILE: test/spec/features/modeling/behavior/ResizeBehaviorSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import coreModule from 'lib/core';
import modelingModule from 'lib/features/modeling';
import resizeModule from 'diagram-js/lib/features/resize';
import rulesModule from 'lib/features/rules';
import snappingModule from 'lib/features/snapping';
import {
createCanvasEvent as canvasEvent
} from '../../../../util/MockEvents';
import {
getParticipantResizeConstraints
} from 'lib/features/modeling/behavior/ResizeBehavior';
var testModules = [
coreModule,
modelingModule,
resizeModule,
rulesModule,
snappingModule
];
describe('features/modeling - resize behavior', function() {
describe('participant', function() {
describe('minimum dimensions', function() {
var diagramXML = require('./ResizeBehavior.participant.bpmn');
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
it('should snap to children from ', inject(function(dragging, elementRegistry, resize) {
// given
var participant = elementRegistry.get('Participant_2');
// when
resize.activate(canvasEvent({ x: 500, y: 500 }), participant, 'se');
dragging.move(canvasEvent({ x: 0, y: 0 }));
dragging.end();
// then
expect(participant.width).to.equal(482);
expect(participant.height).to.equal(252);
}));
it('should snap to children from ', inject(function(dragging, elementRegistry, resize) {
// given
var participant = elementRegistry.get('Participant_2');
// when
resize.activate(canvasEvent({ x: 0, y: 0 }), participant, 'nw');
dragging.move(canvasEvent({ x: 500, y: 500 }));
dragging.end();
// then
expect(participant.width).to.equal(467);
expect(participant.height).to.equal(287);
}));
it('should snap to min dimensions from ', inject(
function(dragging, elementRegistry, resize) {
// given
var participant = elementRegistry.get('Participant_1');
// when
resize.activate(canvasEvent({ x: 500, y: 500 }), participant, 'se');
dragging.move(canvasEvent({ x: 0, y: 0 }));
dragging.end();
// then
expect(participant.width).to.equal(300);
expect(participant.height).to.equal(60);
})
);
it('should snap to min dimensions from ', inject(
function(dragging, elementRegistry, resize) {
// given
var participant = elementRegistry.get('Participant_1');
// when
resize.activate(canvasEvent({ x: 0, y: 0 }), participant, 'nw');
dragging.move(canvasEvent({ x: 500, y: 500 }));
dragging.end();
// then
expect(participant.width).to.equal(300);
expect(participant.height).to.equal(60);
})
);
it('should snap to min dimensions + children from ', inject(
function(dragging, elementRegistry, resize) {
// given
var participant = elementRegistry.get('Participant_3');
// when
resize.activate(canvasEvent({ x: 500, y: 500 }), participant, 'se');
dragging.move(canvasEvent({ x: 0, y: 0 }));
dragging.end();
// then
expect(participant.width).to.equal(305);
expect(participant.height).to.equal(143);
})
);
it('should snap to min dimensions + children from ', inject(
function(dragging, elementRegistry, resize) {
// given
var participant = elementRegistry.get('Participant_3');
// when
resize.activate(canvasEvent({ x: 0, y: 0 }), participant, 'nw');
dragging.move(canvasEvent({ x: 500, y: 500 }));
dragging.end();
// then
expect(participant.width).to.equal(353);
expect(participant.height).to.equal(177);
})
);
});
describe('vertical minimum dimensions', function() {
var diagramXML = require('./ResizeBehavior.participant.vertical.bpmn');
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
it('should snap to min dimensions from ', inject(
function(dragging, elementRegistry, resize) {
// given
var participant = elementRegistry.get('Vertical_Participant_1');
// when
resize.activate(canvasEvent({ x: 500, y: 500 }), participant, 'se');
dragging.move(canvasEvent({ x: 0, y: 0 }));
dragging.end();
// then
expect(participant.width).to.equal(60);
expect(participant.height).to.equal(300);
})
);
it('should snap to min dimensions from ', inject(
function(dragging, elementRegistry, resize) {
// given
var participant = elementRegistry.get('Vertical_Participant_1');
// when
resize.activate(canvasEvent({ x: 0, y: 0 }), participant, 'nw');
dragging.move(canvasEvent({ x: 500, y: 500 }));
dragging.end();
// then
expect(participant.width).to.equal(60);
expect(participant.height).to.equal(300);
})
);
it('should snap to children from ', inject(function(dragging, elementRegistry, resize) {
// given
var participant = elementRegistry.get('Vertical_Participant_2');
// when
resize.activate(canvasEvent({ x: 500, y: 500 }), participant, 'se');
dragging.move(canvasEvent({ x: 0, y: 0 }));
dragging.end();
// then
expect(participant.width).to.equal(293);
expect(participant.height).to.equal(433);
}));
it('should snap to children from ', inject(function(dragging, elementRegistry, resize) {
// given
var participant = elementRegistry.get('Vertical_Participant_2');
// when
resize.activate(canvasEvent({ x: 0, y: 0 }), participant, 'nw');
dragging.move(canvasEvent({ x: 500, y: 500 }));
dragging.end();
// then
expect(participant.width).to.equal(279);
expect(participant.height).to.equal(467);
}));
it('should snap to min dimensions + children from ', inject(
function(dragging, elementRegistry, resize) {
// given
var participant = elementRegistry.get('Vertical_Participant_3');
// when
resize.activate(canvasEvent({ x: 500, y: 500 }), participant, 'se');
dragging.move(canvasEvent({ x: 0, y: 0 }));
dragging.end();
// then
expect(participant.width).to.equal(163);
expect(participant.height).to.equal(345);
})
);
it('should snap to min dimensions + children from ', inject(
function(dragging, elementRegistry, resize) {
// given
var participant = elementRegistry.get('Vertical_Participant_3');
// when
resize.activate(canvasEvent({ x: 0, y: 0 }), participant, 'nw');
dragging.move(canvasEvent({ x: 500, y: 500 }));
dragging.end();
// then
expect(participant.width).to.equal(177);
expect(participant.height).to.equal(353);
})
);
});
describe('resize constraints', function() {
var diagramXML = require('./ResizeBehavior.lanes.bpmn');
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
it('should snap to child lanes from ', inject(
function(dragging, elementRegistry, resize) {
// given
var participant = elementRegistry.get('Participant');
// when
resize.activate(canvasEvent({ x: 0, y: 0 }), participant, 'nw');
dragging.move(canvasEvent({ x: 500, y: 500 }));
dragging.end();
// then
expect(participant.width).to.equal(563);
expect(participant.height).to.equal(223);
})
);
it('should snap to nested child lanes from ', inject(
function(dragging, elementRegistry, resize) {
// given
var lane = elementRegistry.get('Lane_B_0');
// when
resize.activate(canvasEvent({ x: 0, y: 0 }), lane, 'se');
dragging.move(canvasEvent({ x: -500, y: -500 }));
dragging.end();
// then
expect(lane.width).to.equal(330);
expect(lane.height).to.equal(122);
})
);
it('should snap to nested child lanes from ', inject(
function(dragging, elementRegistry, resize) {
// given
var lane = elementRegistry.get('Lane_A');
// when
resize.activate(canvasEvent({ x: 0, y: 0 }), lane, 'ne');
dragging.move(canvasEvent({ x: -500, y: 500 }));
dragging.end();
// then
expect(lane.width).to.equal(330);
expect(lane.height).to.equal(105);
})
);
it('should snap to nested child lanes from ', inject(
function(dragging, elementRegistry, resize) {
// given
var lane = elementRegistry.get('Lane_A');
// when
resize.activate(canvasEvent({ x: 0, y: 0 }), lane, 'sw');
dragging.move(canvasEvent({ x: 500, y: -500 }));
dragging.end();
// then
expect(lane.width).to.equal(563);
expect(lane.height).to.equal(60);
})
);
it('should snap to other sibling lanes child participants from ', inject(
function(dragging, elementRegistry, resize) {
// given
var lane = elementRegistry.get('Lane_B_0');
// when
resize.activate(canvasEvent({ x: 0, y: 0 }), lane, 'nw');
dragging.move(canvasEvent({ x: 500, y: 500 }));
dragging.end();
// then
expect(lane.width).to.equal(563);
expect(lane.height).to.equal(60);
})
);
});
describe('vertical resize constraints', function() {
var diagramXML = require('./ResizeBehavior.lanes.vertical.bpmn');
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
it('should snap to child lanes from ', inject(
function(dragging, elementRegistry, resize) {
// given
var participant = elementRegistry.get('Vertical_Participant');
// when
resize.activate(canvasEvent({ x: 0, y: 0 }), participant, 'nw');
dragging.move(canvasEvent({ x: 500, y: 500 }));
dragging.end();
// then
expect(participant.width).to.equal(283);
expect(participant.height).to.equal(563);
})
);
it('should snap to nested child lanes from ', inject(
function(dragging, elementRegistry, resize) {
// given
var lane = elementRegistry.get('Vertical_Lane_B_0');
// when
resize.activate(canvasEvent({ x: 0, y: 0 }), lane, 'se');
dragging.move(canvasEvent({ x: -500, y: -500 }));
dragging.end();
// then
expect(lane.width).to.equal(142);
expect(lane.height).to.equal(330);
})
);
});
});
describe('sub process', function() {
var diagramXML = require('./ResizeBehavior.subProcess.bpmn');
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
it('should set minimum dimensions', inject(function(dragging, elementRegistry, resize) {
// given
var subProcess = elementRegistry.get('SubProcess');
// when
resize.activate(canvasEvent({ x: 0, y: 0 }), subProcess, 'se');
dragging.move(canvasEvent({ x: -400, y: -400 }));
dragging.end();
// then
expect(subProcess.width).to.equal(140);
expect(subProcess.height).to.equal(120);
}));
});
describe('text annotation', function() {
var diagramXML = require('./ResizeBehavior.textAnnotation.bpmn');
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
it('should set minimum dimensions', inject(function(dragging, elementRegistry, resize) {
// given
var textAnnotation = elementRegistry.get('TextAnnotation');
// when
resize.activate(canvasEvent({ x: 0, y: 0 }), textAnnotation, 'se');
dragging.move(canvasEvent({ x: -400, y: -400 }));
dragging.end();
// then
expect(textAnnotation.width).to.equal(50);
expect(textAnnotation.height).to.equal(30);
}));
});
});
var LANE_MIN_HEIGHT = 60,
LANE_RIGHT_PADDING = 20,
LANE_LEFT_PADDING = 50,
LANE_TOP_PADDING = 20,
LANE_BOTTOM_PADDING = 20,
VERTICAL_LANE_MIN_WIDTH = 60,
VERTICAL_LANE_TOP_PADDING = 50,
VERTICAL_LANE_LEFT_PADDING = 20;
describe('modeling/behavior - resize behavior - utilities', function() {
describe('#getParticipantResizeConstraints', function() {
describe('lanes', function() {
var diagramXML = require('./ResizeBehavior.utility.lanes.bpmn');
beforeEach(bootstrapModeler(diagramXML, { modules: [ coreModule ] }));
it('resize participant (S)', inject(function(elementRegistry) {
// given
var resizeShape = elementRegistry.get('Participant_Lane'),
otherLaneShape = elementRegistry.get('Lane_B');
// when
var sizeConstraints = getParticipantResizeConstraints(resizeShape, 's');
// then
expect(sizeConstraints).to.eql({
min: {
bottom: otherLaneShape.y + LANE_MIN_HEIGHT
},
max: {}
});
}));
it('bottom lane (S)', inject(function(elementRegistry) {
// given
var resizeShape = elementRegistry.get('Lane_B'),
otherLaneShape = elementRegistry.get('Lane_B');
// when
var sizeConstraints = getParticipantResizeConstraints(resizeShape, 's');
// then
expect(sizeConstraints).to.eql({
min: {
bottom: otherLaneShape.y + LANE_MIN_HEIGHT
},
max: {}
});
}));
it('resize participant (N)', inject(function(elementRegistry) {
// given
var resizeShape = elementRegistry.get('Participant_Lane'),
otherLaneShape = elementRegistry.get('Nested_Lane_A');
// when
var sizeConstraints = getParticipantResizeConstraints(resizeShape, 'n');
// then
expect(sizeConstraints).to.eql({
min: {
top: otherLaneShape.y + otherLaneShape.height - LANE_MIN_HEIGHT
},
max: {}
});
}));
it('resize top lane (N)', inject(function(elementRegistry) {
// given
var resizeShape = elementRegistry.get('Lane_A'),
otherLaneShape = elementRegistry.get('Nested_Lane_A');
// when
var sizeConstraints = getParticipantResizeConstraints(resizeShape, 'n');
// then
expect(sizeConstraints).to.eql({
min: {
top: otherLaneShape.y + otherLaneShape.height - LANE_MIN_HEIGHT
},
max: {}
});
}));
it('resize middle lane (N)', inject(function(elementRegistry) {
// given
var resizeShape = elementRegistry.get('Nested_Lane_B'),
aboveLaneShape = elementRegistry.get('Nested_Lane_A');
// when
var sizeConstraints = getParticipantResizeConstraints(resizeShape, 'n', true);
// then
expect(sizeConstraints).to.eql({
min: {
top: resizeShape.y + resizeShape.height - LANE_MIN_HEIGHT
},
max: {
top: aboveLaneShape.y + LANE_MIN_HEIGHT
}
});
}));
it('resize middle lane (S)', inject(function(elementRegistry) {
// given
var resizeShape = elementRegistry.get('Nested_Lane_B'),
otherLaneShape = elementRegistry.get('Lane_B');
// when
var sizeConstraints = getParticipantResizeConstraints(resizeShape, 's', true);
// then
expect(sizeConstraints).to.eql({
min: {
bottom: resizeShape.y + LANE_MIN_HEIGHT
},
max: {
bottom: otherLaneShape.y + otherLaneShape.height - LANE_MIN_HEIGHT
}
});
}));
});
describe('flowNodes', function() {
var diagramXML = require('./ResizeBehavior.utility.lanes-flowNodes.bpmn');
beforeEach(bootstrapModeler(diagramXML, { modules: [ coreModule ] }));
it('resize participant (S)', inject(function(elementRegistry) {
// given
var resizeShape = elementRegistry.get('Participant_Lane'),
taskShape = elementRegistry.get('Task');
// when
var sizeConstraints = getParticipantResizeConstraints(resizeShape, 's');
// then
expect(sizeConstraints).to.eql({
min: {
bottom: taskShape.y + taskShape.height + LANE_BOTTOM_PADDING
},
max: {}
});
}));
it('bottom lane (S)', inject(function(elementRegistry) {
// given
var resizeShape = elementRegistry.get('Lane_B'),
taskShape = elementRegistry.get('Task');
// when
var sizeConstraints = getParticipantResizeConstraints(resizeShape, 's');
// then
expect(sizeConstraints).to.eql({
min: {
bottom: taskShape.y + taskShape.height + LANE_BOTTOM_PADDING
},
max: {}
});
}));
it('resize participant (N)', inject(function(elementRegistry) {
// given
var resizeShape = elementRegistry.get('Participant_Lane'),
taskShape = elementRegistry.get('Task_Boundary');
// when
var sizeConstraints = getParticipantResizeConstraints(resizeShape, 'n');
// then
expect(sizeConstraints).to.eql({
min: {
top: taskShape.y - LANE_TOP_PADDING
},
max: {}
});
}));
it('resize top lane (N)', inject(function(elementRegistry) {
// given
var resizeShape = elementRegistry.get('Lane_A'),
taskShape = elementRegistry.get('Task_Boundary');
// when
var sizeConstraints = getParticipantResizeConstraints(resizeShape, 'n');
// then
expect(sizeConstraints).to.eql({
min: {
top: taskShape.y - LANE_TOP_PADDING
},
max: {}
});
}));
it('resize lane (W)', inject(function(elementRegistry) {
// given
var resizeShape = elementRegistry.get('Nested_Lane_B'),
otherShape = elementRegistry.get('Boundary_label');
// when
var sizeConstraints = getParticipantResizeConstraints(resizeShape, 'w');
// then
expect(sizeConstraints).to.eql({
min: {
left: otherShape.x - LANE_LEFT_PADDING
},
max: { }
});
}));
it('resize lane (E)', inject(function(elementRegistry) {
// given
var resizeShape = elementRegistry.get('Lane_B'),
otherShape = elementRegistry.get('Task');
// when
var sizeConstraints = getParticipantResizeConstraints(resizeShape, 'e');
// then
expect(sizeConstraints).to.eql({
min: {
right: otherShape.x + otherShape.width + LANE_RIGHT_PADDING
},
max: { }
});
}));
});
});
describe('vertical #getParticipantResizeConstraints', function() {
describe('lanes', function() {
var diagramXML = require('./ResizeBehavior.utility.lanes.vertical.bpmn');
beforeEach(bootstrapModeler(diagramXML, { modules: [ coreModule ] }));
it('resize participant (E)', inject(function(elementRegistry) {
// given
var resizeShape = elementRegistry.get('Vertical_Participant_Lane'),
otherLaneShape = elementRegistry.get('Vertical_Lane_B');
// when
var sizeConstraints = getParticipantResizeConstraints(resizeShape, 'e');
// then
expect(sizeConstraints).to.eql({
min: {
right: otherLaneShape.x + VERTICAL_LANE_MIN_WIDTH
},
max: {}
});
}));
it('right lane (E)', inject(function(elementRegistry) {
// given
var resizeShape = elementRegistry.get('Vertical_Lane_B'),
otherLaneShape = elementRegistry.get('Vertical_Lane_B');
// when
var sizeConstraints = getParticipantResizeConstraints(resizeShape, 'e');
// then
expect(sizeConstraints).to.eql({
min: {
right: otherLaneShape.x + VERTICAL_LANE_MIN_WIDTH
},
max: {}
});
}));
it('resize participant (W)', inject(function(elementRegistry) {
// given
var resizeShape = elementRegistry.get('Vertical_Participant_Lane'),
otherLaneShape = elementRegistry.get('Nested_Vertical_Lane_A');
// when
var sizeConstraints = getParticipantResizeConstraints(resizeShape, 'w');
// then
expect(sizeConstraints).to.eql({
min: {
left: otherLaneShape.x + otherLaneShape.width - VERTICAL_LANE_MIN_WIDTH
},
max: {}
});
}));
it('resize left lane (L)', inject(function(elementRegistry) {
// given
var resizeShape = elementRegistry.get('Vertical_Lane_A'),
otherLaneShape = elementRegistry.get('Nested_Vertical_Lane_A');
// when
var sizeConstraints = getParticipantResizeConstraints(resizeShape, 'w');
// then
expect(sizeConstraints).to.eql({
min: {
left: otherLaneShape.x + otherLaneShape.width - VERTICAL_LANE_MIN_WIDTH
},
max: {}
});
}));
it('resize middle lane (W)', inject(function(elementRegistry) {
// given
var resizeShape = elementRegistry.get('Nested_Vertical_Lane_B'),
aboveLaneShape = elementRegistry.get('Nested_Vertical_Lane_A');
// when
var sizeConstraints = getParticipantResizeConstraints(resizeShape, 'w', true);
// then
expect(sizeConstraints).to.eql({
min: {
left: resizeShape.x + resizeShape.width - VERTICAL_LANE_MIN_WIDTH
},
max: {
left: aboveLaneShape.x + VERTICAL_LANE_MIN_WIDTH
}
});
}));
it('resize middle lane (E)', inject(function(elementRegistry) {
// given
var resizeShape = elementRegistry.get('Nested_Vertical_Lane_B'),
otherLaneShape = elementRegistry.get('Vertical_Lane_B');
// when
var sizeConstraints = getParticipantResizeConstraints(resizeShape, 'e', true);
// then
expect(sizeConstraints).to.eql({
min: {
right: resizeShape.x + VERTICAL_LANE_MIN_WIDTH
},
max: {
right: otherLaneShape.x + otherLaneShape.width - VERTICAL_LANE_MIN_WIDTH
}
});
}));
});
describe('flowNodes', function() {
var diagramXML = require('./ResizeBehavior.utility.lanes.vertical-flowNodes.bpmn');
beforeEach(bootstrapModeler(diagramXML, { modules: [ coreModule ] }));
it('resize participant (E)', inject(function(elementRegistry) {
// given
var resizeShape = elementRegistry.get('Vertical_Participant_Lane'),
taskShape = elementRegistry.get('Task');
// when
var sizeConstraints = getParticipantResizeConstraints(resizeShape, 'e');
// then
expect(sizeConstraints).to.eql({
min: {
right: taskShape.x + taskShape.width + LANE_RIGHT_PADDING
},
max: {}
});
}));
it('right lane (E)', inject(function(elementRegistry) {
// given
var resizeShape = elementRegistry.get('Vertical_Lane_B'),
taskShape = elementRegistry.get('Task');
// when
var sizeConstraints = getParticipantResizeConstraints(resizeShape, 'e');
// then
expect(sizeConstraints).to.eql({
min: {
right: taskShape.x + taskShape.width + LANE_RIGHT_PADDING
},
max: {}
});
}));
it('left lane (E)', inject(function(elementRegistry) {
// given
var resizeShape = elementRegistry.get('Vertical_Lane_A'),
nestedLaneShape = elementRegistry.get('Nested_Vertical_Lane_B');
// when
var sizeConstraints = getParticipantResizeConstraints(resizeShape, 'e');
// then
expect(sizeConstraints).to.eql({
min: {
right: nestedLaneShape.x + VERTICAL_LANE_MIN_WIDTH
},
max: {}
});
}));
it('nested left lane (E)', inject(function(elementRegistry) {
// given
var resizeShape = elementRegistry.get('Nested_Vertical_Lane_A');
// when
var sizeConstraints = getParticipantResizeConstraints(resizeShape, 'e');
// then
expect(sizeConstraints).to.eql({
min: {
right: resizeShape.x + VERTICAL_LANE_MIN_WIDTH
},
max: {}
});
}));
it('resize participant (W)', inject(function(elementRegistry) {
// given
var resizeShape = elementRegistry.get('Vertical_Participant_Lane'),
taskShape = elementRegistry.get('Task_Boundary');
// when
var sizeConstraints = getParticipantResizeConstraints(resizeShape, 'w');
// then
expect(sizeConstraints).to.eql({
min: {
left: taskShape.x - VERTICAL_LANE_LEFT_PADDING
},
max: {}
});
}));
it('resize left lane (W)', inject(function(elementRegistry) {
// given
var resizeShape = elementRegistry.get('Vertical_Lane_A'),
taskShape = elementRegistry.get('Task_Boundary');
// when
var sizeConstraints = getParticipantResizeConstraints(resizeShape, 'w');
// then
expect(sizeConstraints).to.eql({
min: {
left: taskShape.x - VERTICAL_LANE_LEFT_PADDING
},
max: {}
});
}));
it('resize lane (N)', inject(function(elementRegistry) {
// given
var resizeShape = elementRegistry.get('Nested_Vertical_Lane_B'),
otherShape = elementRegistry.get('Boundary_label');
// when
var sizeConstraints = getParticipantResizeConstraints(resizeShape, 'n');
// then
expect(sizeConstraints).to.eql({
min: {
top: otherShape.y - VERTICAL_LANE_TOP_PADDING
},
max: { }
});
}));
it('resize lane (S)', inject(function(elementRegistry) {
// given
var resizeShape = elementRegistry.get('Vertical_Lane_B'),
otherShape = elementRegistry.get('Task');
// when
var sizeConstraints = getParticipantResizeConstraints(resizeShape, 's');
// then
expect(sizeConstraints).to.eql({
min: {
bottom: otherShape.y + otherShape.height + LANE_BOTTOM_PADDING
},
max: { }
});
}));
});
});
describe('LaneUtil', function() {
describe('lane minimum dimensions', function() {
var diagramXML = require('./ResizeBehavior.utility.lanes.bpmn');
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
it('should hold for top sibling lane', inject(
function(dragging, elementRegistry, resize) {
// given
var lane = elementRegistry.get('Nested_Lane_B');
var topSiblingLane = elementRegistry.get('Nested_Lane_A');
// when
resize.activate(canvasEvent({ x: 0, y: 0 }), lane, 'n');
dragging.move(canvasEvent({ x: 0, y: -500 }));
dragging.end();
// then
expect(lane.height).to.equal(301);
expect(topSiblingLane.height).to.equal(60);
})
);
it('should hold for bottom sibling lane', inject(
function(dragging, elementRegistry, resize) {
// given
var lane = elementRegistry.get('Nested_Lane_B');
var bottomSiblingLane = elementRegistry.get('Lane_B');
// when
resize.activate(canvasEvent({ x: 0, y: 0 }), lane, 's');
dragging.move(canvasEvent({ x: 0, y: 500 }));
dragging.end();
// then
expect(lane.height).to.equal(292);
expect(bottomSiblingLane.height).to.equal(60);
})
);
});
describe('vertical lane minimum dimensions', function() {
var diagramXML = require('./ResizeBehavior.utility.lanes.vertical.bpmn');
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
it('should hold for left sibling lane', inject(
function(dragging, elementRegistry, resize) {
// given
var lane = elementRegistry.get('Nested_Vertical_Lane_B');
var leftSiblingLane = elementRegistry.get('Nested_Vertical_Lane_A');
// when
resize.activate(canvasEvent({ x: 0, y: 0 }), lane, 'w');
dragging.move(canvasEvent({ x: -500, y: 0 }));
dragging.end();
// then
expect(lane.width).to.equal(301);
expect(leftSiblingLane.width).to.equal(60);
})
);
it('should hold for right sibling lane', inject(
function(dragging, elementRegistry, resize) {
// given
var lane = elementRegistry.get('Nested_Vertical_Lane_B');
var rightSiblingLane = elementRegistry.get('Vertical_Lane_B');
// when
resize.activate(canvasEvent({ x: 0, y: 0 }), lane, 'e');
dragging.move(canvasEvent({ x: 500, y: 0 }));
dragging.end();
// then
expect(lane.width).to.equal(292);
expect(rightSiblingLane.width).to.equal(60);
})
);
});
});
});
================================================
FILE: test/spec/features/modeling/behavior/RootElementReferenceBehavior.bpmn
================================================
================================================
FILE: test/spec/features/modeling/behavior/RootElementReferenceBehaviorSpec.js
================================================
import {
bootstrapModeler,
getBpmnJS,
inject
} from 'test/TestHelper';
import coreModule from 'lib/core';
import modelingModule from 'lib/features/modeling';
import {
getBusinessObject,
is
} from 'lib/util/ModelUtil';
import {
remove as collectionRemove
} from 'diagram-js/lib/util/Collections';
import {
filter,
find,
forEach,
matchPattern
} from 'min-dash';
var testModules = [
coreModule,
modelingModule
];
describe('features/modeling - root element reference behavior', function() {
var diagramXML = require('./RootElementReferenceBehavior.bpmn');
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
describe('add root element', function() {
forEach([
'error',
'escalation',
'message',
'signal'
], function(type) {
describe(type, function() {
var id = capitalizeFirstChar(type) + 'BoundaryEvent_1';
var boundaryEvent,
host,
rootElement,
pastedRootElement;
describe('should add a copy', function() {
beforeEach(inject(function(bpmnjs, copyPaste, elementRegistry, modeling) {
// given
boundaryEvent = elementRegistry.get(id);
host = elementRegistry.get('Task_2');
var businessObject = getBusinessObject(boundaryEvent),
eventDefinitions = businessObject.get('eventDefinitions'),
eventDefinition = eventDefinitions[ 0 ];
rootElement = getRootElementReferenced(eventDefinition);
// when
copyPaste.copy(boundaryEvent);
modeling.removeShape(boundaryEvent);
collectionRemove(bpmnjs.getDefinitions().get('rootElements'), rootElement);
expect(hasRootElement(rootElement)).to.be.false;
boundaryEvent = copyPaste.paste({
element: host,
point: {
x: host.x,
y: host.y
},
hints: {
attach: 'attach'
}
})[0];
businessObject = getBusinessObject(boundaryEvent);
pastedRootElement = getRootElementReferenced(
businessObject.get('eventDefinitions')[ 0 ]
);
}));
it('', function() {
// then
expect(hasRootElement(rootElement)).to.be.false;
expect(hasRootElement(pastedRootElement)).to.be.true;
// no garbage attached to element
expect(boundaryEvent.referencedRootElements).not.to.exist;
});
it('', inject(function(commandStack) {
// when
commandStack.undo();
// then
expect(hasRootElement(rootElement)).to.be.false;
expect(hasRootElement(pastedRootElement)).to.be.false;
}));
it('', inject(function(commandStack) {
// given
commandStack.undo();
// when
commandStack.redo();
// then
expect(hasRootElement(rootElement)).to.be.false;
expect(hasRootElement(pastedRootElement)).to.be.true;
// no garbage attached to element
expect(boundaryEvent.referencedRootElements).not.to.exist;
}));
});
it('should NOT add', inject(function(bpmnFactory, bpmnjs, copyPaste, elementRegistry, moddleCopy, modeling) {
// given
boundaryEvent = elementRegistry.get(id);
host = elementRegistry.get('Task_2');
var businessObject = getBusinessObject(boundaryEvent),
eventDefinitions = businessObject.get('eventDefinitions'),
eventDefinition = eventDefinitions[ 0 ],
rootElements = bpmnjs.getDefinitions().get('rootElements');
rootElement = getRootElementReferenced(eventDefinition);
var rootElementsOfTypeCount = filter(
rootElements, matchPattern({ $type: rootElement.$type })
).length;
copyPaste.copy(boundaryEvent);
modeling.removeShape(boundaryEvent);
collectionRemove(rootElements, rootElement);
expect(hasRootElement(rootElement)).to.be.false;
var rootElementWithSameId = bpmnFactory.create(rootElement.$type);
moddleCopy.copyElement(rootElement, rootElementWithSameId);
collectionRemove(rootElements, rootElementWithSameId);
// when
boundaryEvent = copyPaste.paste({
element: host,
point: {
x: host.x,
y: host.y
},
hints: {
attach: 'attach'
}
})[0];
// then
var rootElementsOfType = filter(rootElements, matchPattern({ $type: rootElement.$type }));
expect(rootElementsOfType).to.have.lengthOf(rootElementsOfTypeCount);
}));
});
describe(`${type} (modeling#updateProperties)`, function() {
var boundaryEvent,
rootElement;
describe('should add root element if not added to diagram', function() {
beforeEach(inject(function(bpmnFactory, elementRegistry, modeling) {
// given
boundaryEvent = elementRegistry.get('BoundaryEvent');
rootElement = bpmnFactory.create(`bpmn:${capitalizeFirstChar(type)}`);
var eventDefinition = bpmnFactory.create(`bpmn:${capitalizeFirstChar(type)}EventDefinition`, {
[`${type}Ref`]: rootElement
});
// when
modeling.updateProperties(boundaryEvent, {
eventDefinitions: [
eventDefinition
]
});
}));
it('', function() {
// then
expect(hasRootElement(rootElement)).to.be.true;
// no garbage attached to element
expect(boundaryEvent.referencedRootElements).not.to.exist;
});
it('', inject(function(commandStack) {
// when
commandStack.undo();
// then
expect(hasRootElement(rootElement)).to.be.false;
// no garbage attached to element
expect(boundaryEvent.referencedRootElements).not.to.exist;
}));
it('', inject(function(commandStack) {
// given
commandStack.undo();
// when
commandStack.redo();
// then
expect(hasRootElement(rootElement)).to.be.true;
// no garbage attached to element
expect(boundaryEvent.referencedRootElements).not.to.exist;
}));
});
it('should NOT add root element to root elements if already present', inject(function(
bpmnFactory, bpmnjs, elementRegistry, modeling) {
// given
var rootElements = bpmnjs.getDefinitions().get('rootElements');
boundaryEvent = elementRegistry.get('BoundaryEvent');
rootElement = rootElements.find(matchPattern({ id: `${capitalizeFirstChar(type)}_1` }));
var rootElementsOfTypeCount = filter(
rootElements, matchPattern({ $type: rootElement.$type })
).length;
var eventDefinition = bpmnFactory.create(`bpmn:${capitalizeFirstChar(type)}EventDefinition`, {
[`${type}Ref`]: rootElement
});
// when
modeling.updateProperties(boundaryEvent, {
eventDefinitions: [
eventDefinition
]
});
// then
var rootElementsOfType = filter(rootElements, matchPattern({ $type: rootElement.$type }));
expect(rootElementsOfType).to.have.lengthOf(rootElementsOfTypeCount);
}));
});
describe(`${type} (modeling#updateModdleProperties)`, function() {
var boundaryEvent,
rootElement;
describe('should add root element if not added to diagram', function() {
beforeEach(inject(function(bpmnFactory, elementRegistry, modeling) {
// given
boundaryEvent = elementRegistry.get('BoundaryEvent');
rootElement = bpmnFactory.create(`bpmn:${capitalizeFirstChar(type)}`);
var bo = getBusinessObject(boundaryEvent);
var eventDefinition = bpmnFactory.create(`bpmn:${capitalizeFirstChar(type)}EventDefinition`, {
[`${type}Ref`]: rootElement
});
// when
modeling.updateModdleProperties(boundaryEvent, bo, {
eventDefinitions: [
eventDefinition
]
});
}));
it('', function() {
// then
expect(hasRootElement(rootElement)).to.be.true;
// no garbage attached to element
expect(boundaryEvent.referencedRootElements).not.to.exist;
});
it('', inject(function(commandStack) {
// when
commandStack.undo();
// then
expect(hasRootElement(rootElement)).to.be.false;
// no garbage attached to element
expect(boundaryEvent.referencedRootElements).not.to.exist;
}));
it('', inject(function(commandStack) {
// given
commandStack.undo();
// when
commandStack.redo();
// then
expect(hasRootElement(rootElement)).to.be.true;
// no garbage attached to element
expect(boundaryEvent.referencedRootElements).not.to.exist;
}));
});
it('should NOT add root element to root elements if already present', inject(function(
bpmnFactory, bpmnjs, elementRegistry, modeling) {
// given
var rootElements = bpmnjs.getDefinitions().get('rootElements');
boundaryEvent = elementRegistry.get('BoundaryEvent');
rootElement = rootElements.find(matchPattern({ id: `${capitalizeFirstChar(type)}_1` }));
var rootElementsOfTypeCount = filter(
rootElements, matchPattern({ $type: rootElement.$type })
).length;
var bo = getBusinessObject(boundaryEvent);
var eventDefinition = bpmnFactory.create(`bpmn:${capitalizeFirstChar(type)}EventDefinition`, {
[`${type}Ref`]: rootElement
});
// when
modeling.updateProperties(boundaryEvent, bo, {
eventDefinitions: [
eventDefinition
]
});
// then
var rootElementsOfType = filter(rootElements, matchPattern({ $type: rootElement.$type }));
expect(rootElementsOfType).to.have.lengthOf(rootElementsOfTypeCount);
}));
});
});
describe('receive and send task', function() {
forEach([
'ReceiveTask',
'SendTask'
], function(type) {
var id = type;
var task,
rootElement,
pastedRootElement;
describe('should add a copy', function() {
beforeEach(inject(function(bpmnjs, copyPaste, elementRegistry, modeling, canvas) {
// given
task = elementRegistry.get(id);
var businessObject = getBusinessObject(task),
rootElement = businessObject.messageRef;
// when
copyPaste.copy(task);
modeling.removeShape(task);
collectionRemove(bpmnjs.getDefinitions().get('rootElements'), rootElement);
expect(hasRootElement(rootElement)).to.be.false;
task = copyPaste.paste({
element: canvas.getRootElement(),
point: {
x: task.x,
y: task.y + 200
}
})[0];
businessObject = getBusinessObject(task);
pastedRootElement = businessObject.messageRef;
}));
it('', function() {
// then
expect(hasRootElement(rootElement)).to.be.false;
expect(hasRootElement(pastedRootElement)).to.be.true;
});
it('', inject(function(commandStack) {
// when
commandStack.undo();
// then
expect(hasRootElement(rootElement)).to.be.false;
expect(hasRootElement(pastedRootElement)).to.be.false;
}));
it('', inject(function(commandStack) {
// given
commandStack.undo();
// when
commandStack.redo();
// then
expect(hasRootElement(rootElement)).to.be.false;
expect(hasRootElement(pastedRootElement)).to.be.true;
}));
});
it('should NOT add', inject(function(
bpmnFactory, bpmnjs, copyPaste, elementRegistry, moddleCopy, modeling, canvas
) {
// given
task = elementRegistry.get(id);
var businessObject = getBusinessObject(task),
rootElement = businessObject.messageRef,
rootElements = bpmnjs.getDefinitions().get('rootElements');
var rootElementsOfTypeCount = filter(
rootElements, matchPattern({ $type: rootElement.$type })
).length;
copyPaste.copy(task);
modeling.removeShape(task);
collectionRemove(rootElements, rootElement);
expect(hasRootElement(rootElement)).to.be.false;
var rootElementWithSameId = bpmnFactory.create(rootElement.$type);
moddleCopy.copyElement(rootElement, rootElementWithSameId);
collectionRemove(rootElements, rootElementWithSameId);
// when
task = copyPaste.paste({
element: canvas.getRootElement(),
point: {
x: task.x,
y: task.y + 200
}
})[0];
// then
var rootElementsOfType = filter(rootElements, matchPattern({ $type: rootElement.$type }));
expect(rootElementsOfType).to.have.lengthOf(rootElementsOfTypeCount);
}));
});
describe('modeling#updateProperties', function() {
forEach([
'ReceiveTask_noRef',
'SendTask_noRef'
], function(type) {
var id = type;
var task,
rootElement;
describe('should add on modeling#updateProperties', function() {
beforeEach(inject(function(bpmnFactory, elementRegistry, modeling) {
// given
task = elementRegistry.get(id);
rootElement = bpmnFactory.create('bpmn:Message', { id: 'NewMessage' });
// when
modeling.updateProperties(task, {
messageRef: rootElement,
});
}));
it('', function() {
// then
expect(hasRootElement(rootElement)).to.be.true;
});
it('', inject(function(commandStack) {
// when
commandStack.undo();
// then
expect(hasRootElement(rootElement)).to.be.false;
}));
it('', inject(function(commandStack) {
// given
commandStack.undo();
// when
commandStack.redo();
// then
expect(hasRootElement(rootElement)).to.be.true;
}));
});
it('should NOT add message to root elements if already present', inject(function(
bpmnjs, elementRegistry, modeling
) {
// given
task = elementRegistry.get(id);
var bo = getBusinessObject(task);
var rootElements = bpmnjs.getDefinitions().get('rootElements');
var message = rootElements.find(matchPattern({ id: 'Message_2' }));
var rootElementsOfTypeCount = filter(
rootElements, matchPattern({ $type: 'bpmn:Message' })
).length;
// when
modeling.updateProperties(task, {
messageRef: message,
});
// then
var rootElementsOfType = filter(rootElements, matchPattern({ $type: 'bpmn:Message' }));
expect(rootElementsOfType).to.have.lengthOf(rootElementsOfTypeCount);
expect(bo.get('messageRef')).to.eq(message);
}));
});
});
describe('modeling#updateModdleProperties', function() {
forEach([
'ReceiveTask_noRef',
'SendTask_noRef'
], function(type) {
var id = type;
var task,
rootElement;
describe('should add message to root elements', function() {
beforeEach(inject(function(bpmnFactory, elementRegistry, modeling) {
// given
task = elementRegistry.get(id);
var bo = getBusinessObject(task);
rootElement = bpmnFactory.create('bpmn:Message', { id: 'NewMessage' });
// when
modeling.updateModdleProperties(task, bo, {
messageRef: rootElement,
});
}));
it('', function() {
// then
expect(hasRootElement(rootElement)).to.be.true;
});
it('', inject(function(commandStack) {
// when
commandStack.undo();
// then
expect(hasRootElement(rootElement)).to.be.false;
}));
it('', inject(function(commandStack) {
// given
commandStack.undo();
// when
commandStack.redo();
// then
expect(hasRootElement(rootElement)).to.be.true;
}));
});
it('should NOT add message to root elements if already present', inject(function(
bpmnjs, elementRegistry, modeling
) {
// given
task = elementRegistry.get(id);
var bo = getBusinessObject(task);
var rootElements = bpmnjs.getDefinitions().get('rootElements');
var message = rootElements.find(matchPattern({ id: 'Message_2' }));
var rootElementsOfTypeCount = filter(
rootElements, matchPattern({ $type: 'bpmn:Message' })
).length;
// when
modeling.updateModdleProperties(task, bo, {
messageRef: message,
});
// then
var rootElementsOfType = filter(rootElements, matchPattern({ $type: 'bpmn:Message' }));
expect(rootElementsOfType).to.have.lengthOf(rootElementsOfTypeCount);
expect(bo.get('messageRef')).to.eq(message);
}));
});
});
});
});
describe('copy root element reference', function() {
forEach([
'error',
'escalation',
'message',
'signal'
], function(type) {
describe(type, function() {
var id = capitalizeFirstChar(type) + 'BoundaryEvent_1';
var boundaryEvent,
host,
rootElement;
beforeEach(inject(function(copyPaste, elementRegistry) {
// given
boundaryEvent = elementRegistry.get(id);
host = elementRegistry.get('Task_2');
var businessObject = getBusinessObject(boundaryEvent),
eventDefinitions = businessObject.get('eventDefinitions'),
eventDefinition = eventDefinitions[ 0 ];
rootElement = getRootElementReferenced(eventDefinition);
copyPaste.copy(boundaryEvent);
// when
boundaryEvent = copyPaste.paste({
element: host,
point: {
x: host.x,
y: host.y
},
hints: {
attach: 'attach'
}
})[0];
}));
it('should copy root element reference', function() {
// then
var businessObject = getBusinessObject(boundaryEvent),
eventDefinitions = businessObject.get('eventDefinitions'),
eventDefinition = eventDefinitions[ 0 ];
expect(getRootElementReferenced(eventDefinition)).to.equal(rootElement);
});
});
});
describe('receive and send task', function() {
forEach([
'ReceiveTask',
'SendTask'
], function(type) {
var id = type,
task,
rootElement;
beforeEach(inject(function(copyPaste, elementRegistry, canvas) {
// given
task = elementRegistry.get(id);
var businessObject = getBusinessObject(task);
rootElement = businessObject.messageRef;
copyPaste.copy(task);
// when
task = copyPaste.paste({
element: canvas.getRootElement(),
point: {
x: task.x,
y: task.y + 200,
}
})[0];
}));
it('should copy root element reference', function() {
// then
var businessObject = getBusinessObject(task),
copiedRootElement = businessObject.messageRef;
expect(copiedRootElement).to.equal(rootElement);
});
});
});
});
});
// helpers //////////
function getRootElementReferenced(eventDefinition) {
if (is(eventDefinition, 'bpmn:ErrorEventDefinition')) {
return eventDefinition.get('errorRef');
} else if (is(eventDefinition, 'bpmn:EscalationEventDefinition')) {
return eventDefinition.get('escalationRef');
} else if (is(eventDefinition, 'bpmn:MessageEventDefinition')) {
return eventDefinition.get('messageRef');
} else if (is(eventDefinition, 'bpmn:SignalEventDefinition')) {
return eventDefinition.get('signalRef');
}
}
function hasRootElement(rootElement) {
var definitions = getBpmnJS().getDefinitions(),
rootElements = definitions.get('rootElements');
return !!rootElement && !!find(rootElements, matchPattern({ id: rootElement.id }));
}
function capitalizeFirstChar(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}
================================================
FILE: test/spec/features/modeling/behavior/SetCompensationActivityAfterPasteBehaviorSpec.bpmn
================================================
Flow_1ank1yi
Flow_1ank1yi
================================================
FILE: test/spec/features/modeling/behavior/SetCompensationActivityAfterPasteBehaviorSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import modelingModule from 'lib/features/modeling';
import coreModule from 'lib/core';
import { is } from 'lib/util/ModelUtil';
import copyPasteModule from 'lib/features/copy-paste';
import diagramXML from './SetCompensationActivityAfterPasteBehaviorSpec.bpmn';
describe('features/modeling/behavior - compensation activity after paste', function() {
const testModules = [
copyPasteModule,
coreModule,
modelingModule
];
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
describe('copy/paste compensation activity', function() {
it('without boundary event', inject(function(canvas, elementRegistry, copyPaste) {
// given
copyPaste.copy([ elementRegistry.get('Compensation_Activity') ]);
// when
var copiedElements = copyPaste.paste({
element: canvas.getRootElement(),
point: {
x: 100,
y: 100
}
});
// then
expect(copiedElements).to.have.lengthOf(1);
const taskElement = copiedElements.find(element => is(element, 'bpmn:Task'));
expect(taskElement.businessObject.isForCompensation).to.be.false;
}));
it('with boundary event', inject(function(canvas, elementRegistry, copyPaste) {
// given
copyPaste.copy([
elementRegistry.get('Compensation_Boundary_Task'),
elementRegistry.get('Compensation_Activity') ]);
// when
var copiedElements = copyPaste.paste({
element: canvas.getRootElement(),
point: {
x: 100,
y: 100
}
});
// then
expect(copiedElements).to.have.lengthOf(4);
expect(copiedElements.filter(element => is(element, 'bpmn:Association'))).to.have.length(1);
expect(copiedElements.filter(element => is(element, 'bpmn:BoundaryEvent'))).to.have.length(1);
expect(copiedElements.filter(element => is(element, 'bpmn:Task'))).to.have.length(2);
// verify that for every Task element, if businessObject.isForCompensation exists, it should be true
copiedElements.filter(element => is(element, 'bpmn:Task')).forEach(taskElement => {
if (Object.prototype.hasOwnProperty.call(taskElement.businessObject, 'isForCompensation')) {
expect(taskElement.businessObject.isForCompensation).to.be.true;
}
});
}));
});
});
================================================
FILE: test/spec/features/modeling/behavior/SpaceToolBehaviorSpec.group.bpmn
================================================
================================================
FILE: test/spec/features/modeling/behavior/SpaceToolBehaviorSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import coreModule from 'lib/core';
import modelingModule from 'lib/features/modeling';
import rulesModule from 'lib/features/rules';
import snappingModule from 'lib/features/snapping';
import spaceToolModule from 'diagram-js/lib/features/space-tool';
import {
createCanvasEvent as canvasEvent
} from '../../../../util/MockEvents';
import {
GROUP_MIN_DIMENSIONS,
LANE_MIN_DIMENSIONS,
PARTICIPANT_MIN_DIMENSIONS,
VERTICAL_LANE_MIN_DIMENSIONS,
VERTICAL_PARTICIPANT_MIN_DIMENSIONS,
SUB_PROCESS_MIN_DIMENSIONS
} from 'lib/features/modeling/behavior/ResizeBehavior';
var testModules = [
coreModule,
modelingModule,
rulesModule,
snappingModule,
spaceToolModule
];
describe('features/modeling - space tool behavior', function() {
describe('subprocess', function() {
describe('minimum dimensions', function() {
var diagramXML = require('./SpaceToolBehaviorSpec.subprocess.bpmn');
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
it('should ensure subprocess minimum dimensions', inject(
function(dragging, elementRegistry, spaceTool) {
// given
var subProcess = elementRegistry.get('SubProcess_1');
// when
spaceTool.activateMakeSpace(canvasEvent({ x: 300, y: 0 }));
dragging.move(canvasEvent({ x: 0, y: 0 }));
dragging.end();
// then
expect(subProcess.width).to.equal(SUB_PROCESS_MIN_DIMENSIONS.width);
})
);
});
});
describe('participant', function() {
describe('minimum dimensions', function() {
var diagramXML = require('./SpaceToolBehaviorSpec.participant.bpmn');
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
it('should ensure participant minimum width', inject(
function(dragging, elementRegistry, spaceTool) {
// given
var participant = elementRegistry.get('Participant_1');
// when
spaceTool.activateMakeSpace(canvasEvent({ x: 300, y: 0 }));
dragging.move(canvasEvent({ x: -200, y: 0 }));
dragging.end();
// then
expect(participant.width).to.equal(PARTICIPANT_MIN_DIMENSIONS.width);
})
);
it('should ensure participant minimum height', inject(
function(dragging, elementRegistry, spaceTool) {
// given
var participant = elementRegistry.get('Participant_1');
// when
spaceTool.activateMakeSpace(canvasEvent({ x: 0, y: 100 }));
dragging.move(canvasEvent({ x: 0, y: -400 }));
dragging.end();
// then
expect(participant.height).to.equal(PARTICIPANT_MIN_DIMENSIONS.height);
})
);
it('should ensure lane minimum width', inject(
function(dragging, elementRegistry, spaceTool) {
// given
var participant = elementRegistry.get('Participant_2');
var lane = elementRegistry.get('Lane_1');
// when
spaceTool.activateMakeSpace(canvasEvent({ x: 1200, y: 0 }));
dragging.move(canvasEvent({ x: 0, y: 0 }));
dragging.end();
// then
expect(lane.width).to.equal(LANE_MIN_DIMENSIONS.width);
expect(participant.width).to.equal(LANE_MIN_DIMENSIONS.width + 30);
})
);
it('should ensure lane minimum height', inject(
function(dragging, elementRegistry, spaceTool) {
// given
var lane = elementRegistry.get('Lane_1');
// when
spaceTool.activateMakeSpace(canvasEvent({ x: 0, y: 400 }));
dragging.move(canvasEvent({ x: 0, y: 0 }));
dragging.end();
// then
expect(lane.height).to.equal(LANE_MIN_DIMENSIONS.height);
})
);
it('should ensure nested lane minimum height', inject(
function(dragging, elementRegistry, spaceTool) {
// given
var lane = elementRegistry.get('Lane_6');
// when
spaceTool.activateMakeSpace(canvasEvent({ x: 0, y: 925 }));
dragging.move(canvasEvent({ x: 0, y: 0 }));
dragging.end();
// then
expect(lane.height).to.equal(LANE_MIN_DIMENSIONS.height);
})
);
});
});
describe('vertical participant', function() {
describe('minimum dimensions', function() {
var diagramXML = require('./SpaceToolBehaviorSpec.participant.vertical.bpmn');
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
it('should ensure participant minimum height', inject(
function(dragging, elementRegistry, spaceTool) {
// given
var participant = elementRegistry.get('Vertical_Participant_1');
// when
spaceTool.activateMakeSpace(canvasEvent({ x: 0, y: 300 }));
dragging.move(canvasEvent({ x: 0, y: -200 }));
dragging.end();
// then
expect(participant.height).to.equal(VERTICAL_PARTICIPANT_MIN_DIMENSIONS.height);
})
);
it('should ensure participant minimum width', inject(
function(dragging, elementRegistry, spaceTool) {
// given
var participant = elementRegistry.get('Vertical_Participant_1');
// when
spaceTool.activateMakeSpace(canvasEvent({ x: 100, y: 0 }));
dragging.move(canvasEvent({ x: -400, y: 0 }));
dragging.end();
// then
expect(participant.width).to.equal(VERTICAL_PARTICIPANT_MIN_DIMENSIONS.width);
})
);
it('should ensure lane minimum height', inject(
function(dragging, elementRegistry, spaceTool) {
// given
var participant = elementRegistry.get('Vertical_Participant_2');
var lane = elementRegistry.get('Vertical_Lane_1');
// when
spaceTool.activateMakeSpace(canvasEvent({ x: 0, y: 1200 }));
dragging.move(canvasEvent({ x: 0, y: 0 }));
dragging.end();
// then
expect(lane.height).to.equal(VERTICAL_LANE_MIN_DIMENSIONS.height);
expect(participant.height).to.equal(VERTICAL_LANE_MIN_DIMENSIONS.height + 30);
})
);
it('should ensure lane minimum width', inject(
function(dragging, elementRegistry, spaceTool) {
// given
var lane = elementRegistry.get('Vertical_Lane_1');
// when
spaceTool.activateMakeSpace(canvasEvent({ x: 400, y: 0 }));
dragging.move(canvasEvent({ x: 0, y: 0 }));
dragging.end();
// then
expect(lane.width).to.equal(VERTICAL_LANE_MIN_DIMENSIONS.width);
})
);
it('should ensure nested lane minimum width', inject(
function(dragging, elementRegistry, spaceTool) {
// given
var lane = elementRegistry.get('V_Lane_6');
// when
spaceTool.activateMakeSpace(canvasEvent({ x: 925, y: 0 }));
dragging.move(canvasEvent({ x: 0, y: 0 }));
dragging.end();
// then
expect(lane.width).to.equal(VERTICAL_LANE_MIN_DIMENSIONS.width);
})
);
});
});
describe('group', function() {
describe('minimum dimensions', function() {
var diagramXML = require('./SpaceToolBehaviorSpec.group.bpmn');
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
it('should ensure group minimum dimensions', inject(
function(dragging, elementRegistry, spaceTool) {
// given
var group = elementRegistry.get('Group_1');
// when
spaceTool.activateMakeSpace(canvasEvent({ x: 450, y: 0 }));
dragging.move(canvasEvent({ x: 0, y: 0 }));
dragging.end();
// then
expect(group.width).to.equal(GROUP_MIN_DIMENSIONS.width);
})
);
});
});
});
================================================
FILE: test/spec/features/modeling/behavior/SpaceToolBehaviorSpec.participant.bpmn
================================================
================================================
FILE: test/spec/features/modeling/behavior/SpaceToolBehaviorSpec.participant.vertical.bpmn
================================================
================================================
FILE: test/spec/features/modeling/behavior/SpaceToolBehaviorSpec.subprocess.bpmn
================================================
================================================
FILE: test/spec/features/modeling/behavior/SubProcessBehavior.copy-paste.bpmn
================================================
SequenceFlow_1
SequenceFlow_2
SequenceFlow_1
SequenceFlow_2
SequenceFlow_3
SequenceFlow_3
foo
foo
Flow_0jd8k12
Flow_0d51bg2
Flow_0jd8k12
Flow_0fotq2x
Flow_0d51bg2
Flow_0fotq2x
foo
foo
================================================
FILE: test/spec/features/modeling/behavior/SubProcessBehavior.multiple-planes.bpmn
================================================
================================================
FILE: test/spec/features/modeling/behavior/SubProcessBehavior.nested-subprocess-annotations.bpmn
================================================
Annotation sample
Subprocess annotation
================================================
FILE: test/spec/features/modeling/behavior/SubProcessBehavior.planes.bpmn
================================================
SequenceFlow_1
SequenceFlow_1
================================================
FILE: test/spec/features/modeling/behavior/SubProcessBehavior.start-event.bpmn
================================================
================================================
FILE: test/spec/features/modeling/behavior/SubProcessPlaneBehaviorSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import coreModule from 'lib/core';
import modelingModule from 'lib/features/modeling';
import replaceModule from 'lib/features/replace';
import bpmnCopyPasteModule from 'lib/features/copy-paste';
import copyPasteModule from 'diagram-js/lib/features/copy-paste';
import { is } from 'lib/util/ModelUtil';
import { keys } from 'min-dash';
describe('features/modeling/behavior - subprocess planes', function() {
var diagramXML = require('./SubProcessBehavior.planes.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: [
coreModule,
modelingModule,
replaceModule
]
}));
describe('create', function() {
it('should create new diagram for collapsed subprocess', inject(function(elementFactory, modeling, canvas, bpmnjs) {
// given
var subProcess = elementFactory.createShape({
type: 'bpmn:SubProcess',
isExpanded: false
});
// when
modeling.createShape(subProcess, { x: 300, y: 300 }, canvas.getRootElement());
// then
var diagrams = bpmnjs.getDefinitions().diagrams;
expect(diagrams).to.have.length(2);
expect(canvas.findRoot(planeId(subProcess))).to.exist;
}));
it('should not create new plane for expanded subprocess', inject(function(elementFactory, modeling, canvas, bpmnjs) {
// given
var subProcess = elementFactory.createShape({
type: 'bpmn:SubProcess',
isExpanded: true
});
// when
modeling.createShape(subProcess, { x: 300, y: 300 }, canvas.getRootElement());
// then
var diagrams = bpmnjs.getDefinitions().diagrams;
expect(diagrams).to.have.length(1);
expect(canvas.findRoot(planeId(subProcess))).to.not.exist;
}));
it('should move children to plane for collapsed subprocess', inject(function(elementFactory, modeling, canvas, bpmnjs) {
// given
var subProcess = elementFactory.createShape({
type: 'bpmn:SubProcess',
isExpanded: false
});
var child = elementFactory.createShape({
type: 'bpmn:Task',
parent: subProcess
});
// when
modeling.createElements([ subProcess, child ], { x: 300, y: 300 }, canvas.getRootElement());
// then
var diagrams = bpmnjs.getDefinitions().diagrams;
var newPlane = canvas.findRoot(planeId(subProcess));
expect(diagrams.length).to.equal(2);
expect(newPlane).to.exist;
expect(child.parent).to.equal(newPlane);
}));
it('should move labels to plane for collapsed subprocess', inject(
function(canvas, bpmnReplace, elementRegistry, modeling) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow_1'),
startEvent = elementRegistry.get('StartEvent_1'),
subProcess = elementRegistry.get('SubProcess_2'),
task = elementRegistry.get('Task_2');
// moving label will set its parent to root element
modeling.moveShape(startEvent.label, { x: 0, y: 100 }, subProcess);
// assume
expect(sequenceFlow.parent).to.equal(subProcess);
expect(startEvent.parent).to.equal(subProcess);
expect(startEvent.label.parent).to.equal(canvas.getRootElement());
expect(task.parent).to.equal(subProcess);
// when
bpmnReplace.replaceElement(subProcess, {
type: 'bpmn:SubProcess',
isExpanded: false
});
// then
var plane = elementRegistry.get('SubProcess_2_plane');
expect(sequenceFlow.parent).to.equal(plane);
expect(startEvent.parent).to.equal(plane);
expect(startEvent.label.parent).to.equal(plane);
expect(task.parent).to.equal(plane);
}
));
it('should undo', inject(function(elementFactory, modeling, commandStack, canvas, bpmnjs) {
// given
var subProcess = elementFactory.createShape({
type: 'bpmn:SubProcess',
isExpanded: false
});
modeling.createShape(subProcess, { x: 300, y: 300 }, canvas.getRootElement());
// when
commandStack.undo();
// then
var diagrams = bpmnjs.getDefinitions().diagrams;
expect(diagrams).to.have.length(1);
expect(canvas.findRoot(planeId(subProcess))).to.not.exist;
}));
it('should redo', inject(function(elementFactory, modeling, commandStack, canvas, bpmnjs) {
// given
var subProcess = elementFactory.createShape({
type: 'bpmn:SubProcess',
isExpanded: false
});
modeling.createShape(subProcess, { x: 300, y: 300 }, canvas.getRootElement());
var plane = canvas.findRoot(planeId(subProcess));
// when
commandStack.undo();
commandStack.redo();
// then
var diagrams = bpmnjs.getDefinitions().diagrams;
expect(diagrams).to.have.length(2);
expect(canvas.findRoot(planeId(subProcess))).to.exist;
expect(canvas.findRoot(planeId(subProcess))).to.equal(plane);
}));
});
describe('annotations', function() {
var nestedAnnotationsXML = require('./SubProcessBehavior.nested-subprocess-annotations.bpmn');
beforeEach(bootstrapModeler(nestedAnnotationsXML, {
modules: [
coreModule,
modelingModule,
replaceModule
]
}));
it('should move annotation to plane when collapsed', inject(
function(canvas, bpmnReplace, elementRegistry) {
// given
var subProcess = elementRegistry.get('SubProcess_1'),
annotation = elementRegistry.get('TextAnnotation_1'),
association = elementRegistry.get('Association_1'),
subProcessAnnotation = elementRegistry.get('TextAnnotation_2'),
subProcessAssociation = elementRegistry.get('Association_2'),
startEvent = elementRegistry.get('StartEvent_1');
// assume
expect(annotation.parent).to.equal(canvas.getRootElement());
expect(startEvent.label).to.exist;
// when
bpmnReplace.replaceElement(subProcess, {
type: 'bpmn:SubProcess',
isExpanded: false
});
// then
var plane = elementRegistry.get('SubProcess_1_plane');
expect(annotation.parent).to.equal(plane);
expect(association.parent).to.equal(plane);
expect(startEvent.label.parent).to.equal(plane);
// annotation connected to the sub-process itself should stay outside
expect(subProcessAnnotation.parent).to.equal(canvas.getRootElement());
expect(subProcessAssociation.parent).to.equal(canvas.getRootElement());
}
));
it('should keep only subprocess annotation when connected to both subprocess and inner element', inject(
function(canvas, elementRegistry, modeling) {
// given
var subProcess = elementRegistry.get('SubProcess_1'),
task = elementRegistry.get('Task_1'),
annotation = elementRegistry.get('TextAnnotation_2');
// connect annotation to both subprocess (existing) and inner element
var localAssociation = modeling.connect(task, annotation, {
type: 'bpmn:Association',
associationDirection: 'one'
});
// when
modeling.toggleCollapse(subProcess);
// then
// annotation connected to the sub-process stays outside
expect(annotation.parent).to.equal(canvas.getRootElement());
// local association is removed
expect(elementRegistry.get(localAssociation.id)).to.not.exist;
}
));
it('should move annotation back to process level after collapse and expand', inject(
function(canvas, elementRegistry, modeling) {
// given
var subProcess = elementRegistry.get('SubProcess_1'),
annotation = elementRegistry.get('TextAnnotation_1'),
association = elementRegistry.get('Association_1');
// when
modeling.toggleCollapse(subProcess);
modeling.toggleCollapse(subProcess);
// then
var rootElement = canvas.getRootElement();
expect(annotation.parent).to.equal(rootElement);
expect(association.parent).to.equal(rootElement);
}
));
it('should remove annotation after collapse, expand, and delete', inject(
function(elementRegistry, modeling) {
// given
var subProcess = elementRegistry.get('SubProcess_1');
modeling.toggleCollapse(subProcess);
modeling.toggleCollapse(subProcess);
// when
modeling.removeElements([ subProcess ]);
// then
expect(elementRegistry.get('TextAnnotation_1')).to.not.exist;
expect(elementRegistry.get('Association_1')).to.not.exist;
}
));
it('should remove annotation when subprocess is deleted', inject(
function(elementRegistry, modeling) {
// given
var subProcess = elementRegistry.get('SubProcess_1');
// when
modeling.removeShape(subProcess);
// then
expect(elementRegistry.get('TextAnnotation_1')).to.not.exist;
expect(elementRegistry.get('Association_1')).to.not.exist;
expect(elementRegistry.get('TextAnnotation_2')).to.not.exist;
expect(elementRegistry.get('Association_2')).to.not.exist;
}
));
});
describe('replace', function() {
describe('task -> collapsed subprocess', function() {
it('should add new diagram for collapsed subprocess', inject(
function(elementRegistry, bpmnReplace, bpmnjs, canvas) {
// given
var task = elementRegistry.get('Task_1'),
collapsedSubProcess;
// when
collapsedSubProcess = bpmnReplace.replaceElement(task, {
type: 'bpmn:SubProcess',
isExpanded: false
});
// then
var diagrams = bpmnjs.getDefinitions().diagrams;
expect(diagrams).to.have.length(2);
expect(canvas.findRoot(planeId(collapsedSubProcess))).to.exist;
}
));
it('should undo', inject(
function(elementRegistry, bpmnReplace, bpmnjs, canvas, commandStack) {
// given
var task = elementRegistry.get('Task_1'),
collapsedSubProcess = bpmnReplace.replaceElement(task, {
type: 'bpmn:SubProcess',
isExpanded: false
});
// when
commandStack.undo();
// then
var diagrams = bpmnjs.getDefinitions().diagrams;
expect(diagrams).to.have.length(1);
expect(canvas.findRoot(planeId(collapsedSubProcess))).to.not.exist;
}
));
it('should redo', inject(
function(elementRegistry, bpmnReplace, bpmnjs, canvas, commandStack) {
// given
var task = elementRegistry.get('Task_1'),
collapsedSubProcess = bpmnReplace.replaceElement(task, {
type: 'bpmn:SubProcess',
isExpanded: false
});
// when
commandStack.undo();
commandStack.redo();
// then
var diagrams = bpmnjs.getDefinitions().diagrams;
expect(diagrams).to.have.length(2);
expect(canvas.findRoot(planeId(collapsedSubProcess))).to.exist;
}
));
});
describe('task -> expanded subprocess', function() {
it('should not add new diagram for collapsed subprocess', inject(
function(elementRegistry, bpmnReplace, bpmnjs, canvas) {
// given
var task = elementRegistry.get('Task_1'),
collapsedSubProcess;
// when
collapsedSubProcess = bpmnReplace.replaceElement(task, {
type: 'bpmn:SubProcess',
isExpanded: true
});
// then
var diagrams = bpmnjs.getDefinitions().diagrams;
expect(diagrams).to.have.length(1);
expect(canvas.findRoot(planeId(collapsedSubProcess))).to.not.exist;
}
));
});
});
describe('remove', function() {
var multipleDiagramXML = require('./SubProcessBehavior.multiple-planes.bpmn');
beforeEach(bootstrapModeler(multipleDiagramXML, {
modules: [
coreModule,
modelingModule,
replaceModule
]
}));
it('should recursively remove diagrams', inject(function(elementRegistry, modeling, bpmnjs) {
// given
var subProcess = elementRegistry.get('SubProcess_2');
// when
modeling.removeShape(subProcess);
// then
var nestedTask = elementRegistry.get('nested_task');
var diagrams = bpmnjs.getDefinitions().diagrams;
expect(diagrams).to.have.length(1);
expect(nestedTask).to.not.exist;
}));
it('should undo', inject(function(elementRegistry, modeling, bpmnjs, commandStack) {
// given
var subProcess = elementRegistry.get('SubProcess_2');
modeling.removeShape(subProcess);
// when
commandStack.undo();
// then
var nestedTask = elementRegistry.get('nested_task');
var diagrams = bpmnjs.getDefinitions().diagrams;
expect(diagrams).to.have.length(3);
expect(nestedTask).to.exist;
}));
it('should redo', inject(function(elementRegistry, modeling, bpmnjs, commandStack) {
// given
var subProcess = elementRegistry.get('SubProcess_2');
modeling.removeShape(subProcess);
// when
commandStack.undo();
commandStack.redo();
// then
var nestedTask = elementRegistry.get('nested_task');
var diagrams = bpmnjs.getDefinitions().diagrams;
expect(diagrams).to.have.length(1);
expect(nestedTask).to.not.exist;
}));
});
describe('update', function() {
var multipleDiagramXML = require('./SubProcessBehavior.multiple-planes.bpmn');
beforeEach(bootstrapModeler(multipleDiagramXML, {
modules: [
coreModule,
modelingModule,
replaceModule
]
}));
describe('do', function() {
it('should update plane id when primary shape is changed',
inject(function(modeling, elementRegistry) {
// given
var subProcess = elementRegistry.get('SubProcess_2'),
plane = elementRegistry.get('SubProcess_2_plane');
// when
modeling.updateProperties(subProcess, { id: 'new_name' });
// then
expect(subProcess.id).to.equal('new_name');
expect(plane.id).to.equal('new_name_plane');
}));
it('should update primary shape id when plane is changed',
inject(function(modeling, elementRegistry) {
// given
var subProcess = elementRegistry.get('SubProcess_2'),
plane = elementRegistry.get('SubProcess_2_plane');
// when
modeling.updateProperties(plane, { id: 'new_name' });
// then
expect(subProcess.id).to.equal('new_name');
expect(plane.id).to.equal('new_name_plane');
}));
});
describe('undo', function() {
it('should update plane id when primary shape is changed',
inject(function(modeling, elementRegistry, commandStack) {
// given
var subProcess = elementRegistry.get('SubProcess_2'),
plane = elementRegistry.get('SubProcess_2_plane');
// when
modeling.updateProperties(subProcess, { id: 'new_name' });
commandStack.undo();
// then
expect(subProcess.id).to.equal('SubProcess_2');
expect(plane.id).to.equal('SubProcess_2_plane');
}));
it('should update primary shape id when plane is changed',
inject(function(modeling, elementRegistry, commandStack) {
// given
var subProcess = elementRegistry.get('SubProcess_2'),
plane = elementRegistry.get('SubProcess_2_plane');
// when
modeling.updateProperties(plane, { id: 'new_name' });
commandStack.undo();
// then
expect(subProcess.id).to.equal('SubProcess_2');
expect(plane.id).to.equal('SubProcess_2_plane');
}));
});
describe('redo', function() {
it('should update plane id when primary shape is changed',
inject(function(modeling, elementRegistry, commandStack) {
// given
var subProcess = elementRegistry.get('SubProcess_2'),
plane = elementRegistry.get('SubProcess_2_plane');
// when
modeling.updateProperties(subProcess, { id: 'new_name' });
commandStack.undo();
commandStack.redo();
// then
expect(subProcess.id).to.equal('new_name');
expect(plane.id).to.equal('new_name_plane');
}));
it('should update primary shape id when plane is changed',
inject(function(modeling, elementRegistry, commandStack) {
// given
var subProcess = elementRegistry.get('SubProcess_2'),
plane = elementRegistry.get('SubProcess_2_plane');
// when
modeling.updateProperties(plane, { id: 'new_name' });
commandStack.undo();
commandStack.redo();
// then
expect(subProcess.id).to.equal('new_name');
expect(plane.id).to.equal('new_name_plane');
}));
});
it('should rerender primary shape name when plane is changed',
inject(function(modeling, elementRegistry, eventBus) {
// given
var subProcess = elementRegistry.get('SubProcess_2'),
plane = elementRegistry.get('SubProcess_2_plane');
var changedSpy = sinon.spy();
eventBus.on('element.changed', 5000, changedSpy);
// when
modeling.updateProperties(plane, { name: 'new name' });
// then
expect(changedSpy).to.have.been.calledTwice;
expect(changedSpy.secondCall.args[0].element).to.eql(subProcess);
})
);
});
describe('copy/paste', function() {
var copyXML = require('./SubProcessBehavior.copy-paste.bpmn');
beforeEach(bootstrapModeler(copyXML, {
modules: [
coreModule,
modelingModule,
bpmnCopyPasteModule,
copyPasteModule
]
}));
it('should copy collapsed sub process', inject(function(copyPaste, elementRegistry) {
var subprcoess = elementRegistry.get('SubProcess_3');
// when
var tree = copyPaste.copy([ subprcoess ]);
// then
expect(keys(tree)).to.have.length(3);
expect(tree[ 0 ]).to.have.length(1);
expect(tree[ 1 ]).to.have.length(3);
expect(tree[ 2 ]).to.have.length(12);
}));
it('should paste subprocess plane', inject(
function(canvas, copyPaste, elementRegistry) {
// given
var subprcoess = elementRegistry.get('SubProcess_3'),
rootElement = canvas.getRootElement();
copyPaste.copy(subprcoess);
// when
var elements = copyPaste.paste({
element: rootElement,
point: {
x: 300,
y: 300
}
});
// then
var subprocess = elements[0];
var newRoot = canvas.findRoot(planeId(subprocess));
expect(newRoot).to.exist;
expect(newRoot.children).to.have.length(6);
}
));
it('should undo paste of collapsed subprocess', inject(
function(canvas, commandStack, copyPaste, elementRegistry) {
// given
var subprocess = elementRegistry.get('SubProcess_3'),
rootElement = canvas.getRootElement();
var childrenCount = rootElement.children.length;
copyPaste.copy(subprocess);
copyPaste.paste({
element: rootElement,
point: {
x: 300,
y: 300
}
});
// when
commandStack.undo();
// then
expect(rootElement.children).to.have.length(childrenCount);
}
));
});
});
function planeId(element) {
if (is(element, 'bpmn:SubProcess')) {
return element.id + '_plane';
}
return element.id;
}
================================================
FILE: test/spec/features/modeling/behavior/SubProcessStartEventBehaviorSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import coreModule from 'lib/core';
import modelingModule from 'lib/features/modeling';
import replaceModule from 'lib/features/replace';
import { is, getDi } from 'lib/util/ModelUtil';
describe('features/modeling/behavior - subprocess start event', function() {
var diagramXML = require('./SubProcessBehavior.start-event.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: [
coreModule,
modelingModule,
replaceModule
]
}));
describe('replace', function() {
describe('task -> expanded subprocess', function() {
it('should add start event child to subprocess', inject(
function(elementRegistry, bpmnReplace) {
// given
var task = elementRegistry.get('Task_1'),
expandedSubProcess,
startEvents;
// when
expandedSubProcess = bpmnReplace.replaceElement(task, {
type: 'bpmn:SubProcess',
isExpanded: true
});
// then
startEvents = getChildStartEvents(expandedSubProcess);
expect(startEvents).to.have.length(1);
}
));
it('should wire startEvent di correctly', inject(
function(elementRegistry, bpmnReplace) {
// given
var task = elementRegistry.get('Task_1'),
expandedSubProcess,
startEvent,
startEventDi;
// when
expandedSubProcess = bpmnReplace.replaceElement(task, {
type: 'bpmn:SubProcess',
isExpanded: true
});
// then
startEvent = getChildStartEvents(expandedSubProcess)[0];
startEventDi = getDi(startEvent);
expect(startEventDi.$parent).to.exist;
}
));
});
describe('task -> adhoc subprocess', function() {
it('should NOT add start event child to adhoc subprocess', inject(
function(elementRegistry, bpmnReplace) {
// given
var task = elementRegistry.get('Task_1'),
collapsedSubProcess,
startEvents;
// when
collapsedSubProcess = bpmnReplace.replaceElement(task, {
type: 'bpmn:AdHocSubProcess',
isExpanded: true
});
// then
startEvents = getChildStartEvents(collapsedSubProcess);
expect(startEvents).to.have.length(0);
}));
});
describe('task -> collapsed subprocess', function() {
it('should NOT add start event child to subprocess', inject(
function(elementRegistry, bpmnReplace) {
// given
var task = elementRegistry.get('Task_1'),
collapsedSubProcess,
startEvents;
// when
collapsedSubProcess = bpmnReplace.replaceElement(task, {
type: 'bpmn:SubProcess',
isExpanded: false
});
// then
startEvents = getChildStartEvents(collapsedSubProcess);
expect(startEvents).to.have.length(0);
}
));
});
describe('call activity -> expanded subprocess', function() {
it('should add start event child to subprocess', inject(
function(elementRegistry, bpmnReplace) {
// given
var callActivity = elementRegistry.get('CallActivity_1'),
expandedSubProcess,
startEvents;
// when
expandedSubProcess = bpmnReplace.replaceElement(callActivity, {
type: 'bpmn:SubProcess',
isExpanded: true
});
// then
startEvents = getChildStartEvents(expandedSubProcess);
expect(startEvents).to.have.length(1);
}
));
it('should wire startEvent di correctly', inject(
function(elementRegistry, bpmnReplace) {
// given
var callActivity = elementRegistry.get('CallActivity_1'),
expandedSubProcess,
startEvent,
startEventDi;
// when
expandedSubProcess = bpmnReplace.replaceElement(callActivity, {
type: 'bpmn:SubProcess',
isExpanded: true
});
// then
startEvent = getChildStartEvents(expandedSubProcess)[0];
startEventDi = getDi(startEvent);
expect(startEventDi.$parent).to.exist;
}
));
});
});
});
// helpers //////////
function isStartEvent(element) {
return is(element, 'bpmn:StartEvent');
}
function getChildStartEvents(element) {
return element.children.filter(isStartEvent);
}
================================================
FILE: test/spec/features/modeling/behavior/TextAnnotationBehaviorSpec.bpmn
================================================
================================================
FILE: test/spec/features/modeling/behavior/TextAnnotationBehaviorSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import modelingModule from 'lib/features/modeling';
import coreModule from 'lib/core';
import autoResizeModule from 'lib/features/auto-resize';
describe('features/modeling - TextAnnotationBehavior', function() {
var testModules = [ coreModule, modelingModule, autoResizeModule ];
var processDiagramXML = require('./TextAnnotationBehaviorSpec.bpmn');
beforeEach(bootstrapModeler(processDiagramXML, { modules: testModules }));
var annotation,
task,
subprocess;
beforeEach(inject(function(elementRegistry) {
annotation = elementRegistry.get('TextAnnotation_1');
task = elementRegistry.get('Task_1');
subprocess = elementRegistry.get('Subprocess_1');
}));
it('should NOT resize Container on appending Text Annotation', inject(function(modeling) {
// when
modeling.appendShape(task, { type: 'bpmn:TextAnnotation' });
// then
expect(subprocess.width).to.equal(350);
expect(subprocess.height).to.equal(200);
}));
it('should NOT resize Container on Text Annotation resize', inject(function(modeling) {
// when
modeling.resizeShape(annotation, { x: 0, y: 0, width: 1000, height: 1000 });
// then
expect(subprocess.width).to.equal(350);
expect(subprocess.height).to.equal(200);
}));
it('should NOT resize Container on Text Annotation move', inject(function(modeling) {
// when
modeling.moveShape(annotation, { x: 250, y: 250 });
// then
expect(subprocess.width).to.equal(350);
expect(subprocess.height).to.equal(200);
}));
});
================================================
FILE: test/spec/features/modeling/behavior/ToggleCollapseConnectionBehaviourSpec.bpmn
================================================
Flow_01hgf3n
Flow_01hgf3n
Flow_0h4tfi2
DataStoreReference_03oe1ud
Property_0kwu583
DataStoreReference_03oe1ud
Flow_0h4tfi2
================================================
FILE: test/spec/features/modeling/behavior/ToggleCollapseConnectionBehaviourSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import coreModule from 'lib/core';
import modelingModule from 'lib/features/modeling';
var testModules = [
coreModule,
modelingModule,
];
describe('features/modeling - Toggle Collapse Connection Behavior', function() {
var diagramXML = require('./ToggleCollapseConnectionBehaviourSpec.bpmn');
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
describe('Subprocess', function() {
it('should reconnect flows on collapse', inject(function(elementRegistry, modeling) {
// given
var subProcess = elementRegistry.get('Subprocess_1'),
task = elementRegistry.get('Task_1'),
commentConnection = elementRegistry.get('Association_1'),
incommingDataConnection = elementRegistry.get('DataAssociation_1'),
outgoingDataConnection = elementRegistry.get('DataAssociation_2'),
incommingMessageFlow = elementRegistry.get('MessageFlow_1'),
outgoingMessageFlow = elementRegistry.get('MessageFlow_2');
// when
modeling.toggleCollapse(subProcess);
// then
expect(commentConnection.source).to.equal(task);
expect(incommingDataConnection.target).to.equal(subProcess);
expect(outgoingDataConnection.source).to.equal(subProcess);
expect(incommingMessageFlow.target).to.equal(subProcess);
expect(outgoingMessageFlow.source).to.equal(subProcess);
}));
it('should undo', inject(function(elementRegistry, modeling, commandStack) {
// given
var subProcess = elementRegistry.get('Subprocess_1'),
task = elementRegistry.get('Task_1'),
commentConnection = elementRegistry.get('Association_1'),
incommingDataConnection = elementRegistry.get('DataAssociation_1'),
outgoingDataConnection = elementRegistry.get('DataAssociation_2'),
incommingMessageFlow = elementRegistry.get('MessageFlow_1'),
outgoingMessageFlow = elementRegistry.get('MessageFlow_2');
modeling.toggleCollapse(subProcess);
// when
commandStack.undo();
// then
expect(commentConnection.source).to.equal(task);
expect(incommingDataConnection.target).to.equal(task);
expect(outgoingDataConnection.source).to.equal(task);
expect(incommingMessageFlow.target).to.equal(task);
expect(outgoingMessageFlow.source).to.equal(task);
}));
it('should redo', inject(function(elementRegistry, modeling, commandStack) {
// given
var subProcess = elementRegistry.get('Subprocess_1'),
task = elementRegistry.get('Task_1'),
commentConnection = elementRegistry.get('Association_1'),
incommingDataConnection = elementRegistry.get('DataAssociation_1'),
outgoingDataConnection = elementRegistry.get('DataAssociation_2'),
incommingMessageFlow = elementRegistry.get('MessageFlow_1'),
outgoingMessageFlow = elementRegistry.get('MessageFlow_2');
modeling.toggleCollapse(subProcess);
// when
commandStack.undo();
commandStack.redo();
// then
expect(commentConnection.source).to.equal(task);
expect(incommingDataConnection.target).to.equal(subProcess);
expect(outgoingDataConnection.source).to.equal(subProcess);
expect(incommingMessageFlow.target).to.equal(subProcess);
expect(outgoingMessageFlow.source).to.equal(subProcess);
}));
});
});
================================================
FILE: test/spec/features/modeling/behavior/ToggleElementCollapseBehaviour.bpmn
================================================
SequenceFlow_1
SequenceFlow_4
SequenceFlow_2
SequenceFlow_2
SequenceFlow_5
SequenceFlow_6
SequenceFlow_6
SequenceFlow_7
SequenceFlow_5
SequenceFlow_3
SequenceFlow_3
SequenceFlow_7
SequenceFlow_1
SequenceFlow_4
SequenceFlow_9
SequenceFlow_8
SequenceFlow_9
SequenceFlow_8
SequenceFlow_10
SequenceFlow_10
SequenceFlow_11
SequenceFlow_11
================================================
FILE: test/spec/features/modeling/behavior/ToggleElementCollapseBehaviourSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import modelingModule from 'lib/features/modeling';
import coreModule from 'lib/core';
import {
is,
getDi
} from 'lib/util/ModelUtil';
var testModules = [
modelingModule,
coreModule
];
describe('features/modeling - collapse and expand elements', function() {
var diagramXML = require('./ToggleElementCollapseBehaviour.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
describe('expand', function() {
var defaultSize = {
width: 350,
height: 200
};
it('collapsed-marker is removed',
inject(function(elementRegistry, bpmnReplace) {
// given
var collapsedSubProcess = elementRegistry.get('SubProcess_3');
// when
var expandedSubProcess = bpmnReplace.replaceElement(collapsedSubProcess,
{
type: 'bpmn:SubProcess',
isExpanded: true
}
);
// then +-marker is removed
expect(getDi(expandedSubProcess).isExpanded).to.eql(true);
})
);
it('show all children, but hide empty labels',
inject(function(elementRegistry, bpmnReplace) {
// given
var collapsedSubProcess = elementRegistry.get('SubProcess_1');
var originalChildren = collapsedSubProcess.children.slice();
// when
var expandedSubProcess = bpmnReplace.replaceElement(collapsedSubProcess,
{
type: 'bpmn:SubProcess',
isExpanded: true
}
);
// then keep children
originalChildren.forEach(function(c) {
expect(expandedSubProcess.children).to.include(c);
});
// and show them
expect(expandedSubProcess.children).to.satisfy(allShown());
})
);
it('keep ad-hoc and multiInstance-marker',
inject(function(elementRegistry, bpmnReplace) {
// given
var collapsedAdHocSubProcess = elementRegistry.get('SubProcess_4');
// when
var expandedAdHocSubProcess = bpmnReplace.replaceElement(collapsedAdHocSubProcess,
{
type: 'bpmn:AdHocSubProcess',
isExpanded: true
}
);
// then
expect(is(expandedAdHocSubProcess, 'bpmn:AdHocSubProcess')).to.eql(true);
var businessObject = expandedAdHocSubProcess.businessObject;
expect(businessObject.loopCharacteristics).not.to.be.undefined;
})
);
describe('resizing', function() {
it('ignors hidden children',
inject(function(elementRegistry, bpmnReplace, eventBus) {
// given
var collapsedSubProcess = elementRegistry.get('SubProcess_5');
var hiddenStartEvent = elementRegistry.get('StartEvent_6');
eventBus.once('commandStack.shape.toggleCollapse.postExecute', function(e) {
hiddenStartEvent.hidden = true;
});
// when
var expandedSubProcess = bpmnReplace.replaceElement(collapsedSubProcess,
{
type: 'bpmn:SubProcess',
isExpanded: true
}
);
// then hidden child should not be covered
expect(expandedSubProcess.x).to.be.greaterThan(hiddenStartEvent.x);
expect(expandedSubProcess.y).to.be.greaterThan(hiddenStartEvent.y);
})
);
it('without children is centered and has defaultBounds',
inject(function(elementRegistry, bpmnReplace) {
// given collapsed SubProcess without children
var collapsedSubProcess = elementRegistry.get('SubProcess_3');
var oldMid = {
x: collapsedSubProcess.x + collapsedSubProcess.width / 2,
y: collapsedSubProcess.y + collapsedSubProcess.height / 2
};
// when
var expandedSubProcess = bpmnReplace.replaceElement(collapsedSubProcess,
{
type: 'bpmn:SubProcess',
isExpanded: true
}
);
// then
var newMid = {
x: expandedSubProcess.x + expandedSubProcess.width / 2,
y: expandedSubProcess.y + expandedSubProcess.height / 2
};
expect(newMid).to.eql(oldMid);
expect(expandedSubProcess.width).to.be.at.least(defaultSize.width);
expect(expandedSubProcess.height).to.be.at.least(defaultSize.height);
})
);
it('with children is centered to childrenBoundingBox and has at least defaultBounds',
inject(function(elementRegistry, bpmnReplace) {
// given
var collapsedSubProcess = elementRegistry.get('SubProcess_4');
// when
var expandedSubProcess = bpmnReplace.replaceElement(collapsedSubProcess,
{
type: 'bpmn:SubProcess',
isExpanded: true
}
);
// then
var startEvent = elementRegistry.get('StartEvent_5');
var midChildren = {
x: startEvent.x + startEvent.width / 2,
y: startEvent.y + startEvent.height / 2
};
var expandedMid = {
x: expandedSubProcess.x + expandedSubProcess.width / 2,
y: expandedSubProcess.y + expandedSubProcess.height / 2
};
expect(expandedMid).to.eql(midChildren),
expect(expandedSubProcess.width).to.be.at.least(defaultSize.width);
expect(expandedSubProcess.height).to.be.at.least(defaultSize.height);
})
);
it('to expanding collapsedSubProcess is coverd in childrenBoundingBox',
inject(function(elementRegistry, bpmnReplace) {
// given
var collapsedSubProcess = elementRegistry.get('SubProcess_5');
var collapsedDownRightCorner = {
x: collapsedSubProcess.x + collapsedSubProcess.width,
y: collapsedSubProcess.y + collapsedSubProcess.height
};
// when
var expandedSubProcess = bpmnReplace.replaceElement(collapsedSubProcess,
{
type: 'bpmn:SubProcess',
isExpanded: true
}
);
// then
var expandedDownRightCorner = {
x: expandedSubProcess.x + expandedSubProcess.width,
y: expandedSubProcess.y + expandedSubProcess.height
};
expect(expandedDownRightCorner.x).to.be.at.least(collapsedDownRightCorner.x);
expect(expandedDownRightCorner.y).to.be.at.least(collapsedDownRightCorner.y);
})
);
});
describe('undo', function() {
it('collapsed-marker is placed',
inject(function(elementRegistry, bpmnReplace, commandStack) {
// given
var collapsedSubProcess = elementRegistry.get('SubProcess_1');
var expandedSubProcess = bpmnReplace.replaceElement(collapsedSubProcess,
{
type: 'bpmn:SubProcess',
isExpanded: true
}
);
// when
commandStack.undo();
// then +-marker is placed
expect(getDi(expandedSubProcess).isExpanded).to.eql(false);
})
);
it('restore previous bounds',
inject(function(elementRegistry, bpmnReplace, commandStack) {
// given
var collapsedSubProcess = elementRegistry.get('SubProcess_1');
var originalBounds = {
x: collapsedSubProcess.x,
y: collapsedSubProcess.y,
width: collapsedSubProcess.width,
height: collapsedSubProcess.height
};
bpmnReplace.replaceElement(collapsedSubProcess,
{
type: 'bpmn:SubProcess',
isExpanded: true
}
);
// when
commandStack.undo();
// then
expect(collapsedSubProcess).to.have.bounds(originalBounds);
})
);
it('hide children',
inject(function(elementRegistry, bpmnReplace, commandStack) {
// given
var collapsedSubProcess = elementRegistry.get('SubProcess_1');
var originalChildren = collapsedSubProcess.children.slice();
bpmnReplace.replaceElement(collapsedSubProcess,
{
type: 'bpmn:SubProcess',
isExpanded: true
}
);
// when
commandStack.undo();
// then keep children
originalChildren.forEach(function(c) {
expect(collapsedSubProcess.children).to.include(c);
});
// and hide them
expect(collapsedSubProcess.children).to.satisfy(allHidden());
})
);
});
});
describe('collapse', function() {
var defaultSize = {
width: 100,
height: 80
};
it('collapsed-marker is placed',
inject(function(elementRegistry, bpmnReplace) {
// given
var expandedSubProcess = elementRegistry.get('AdHocSubProcess_1');
// when
var collapsedSubProcess = bpmnReplace.replaceElement(expandedSubProcess,
{
type: 'bpmn:AdHocSubProcess',
isExpanded: false
}
);
// then +-marker is set
expect(getDi(collapsedSubProcess).isExpanded).to.eql(false);
})
);
it('keep ad-hoc and multiInstance-marker',
inject(function(elementRegistry, bpmnReplace) {
// given
var expandedSubProcess = elementRegistry.get('AdHocSubProcess_1');
// when
var collapsedSubProcess = bpmnReplace.replaceElement(expandedSubProcess,
{
type: 'bpmn:AdHocSubProcess',
isExpanded: false
}
);
// then
expect(is(collapsedSubProcess, 'bpmn:AdHocSubProcess')).to.eql(true);
var businessObject = collapsedSubProcess.businessObject;
expect(businessObject.loopCharacteristics).not.to.be.undefined;
})
);
it('moves all children to plane',
inject(function(elementRegistry, bpmnReplace) {
// given
var expandedSubProcess = elementRegistry.get('AdHocSubProcess_1');
var originalChildren = expandedSubProcess.children.slice();
// when
bpmnReplace.replaceElement(expandedSubProcess,
{
type: 'bpmn:SubProcess',
isExpanded: false
}
);
// then
var plane = elementRegistry.get('AdHocSubProcess_1_plane');
originalChildren.forEach(function(c) {
expect(plane.children).to.include(c);
});
})
);
describe('resize', function() {
it('is centered and has default bounds',
inject(function(elementRegistry, bpmnReplace) {
// given
var expandedSubProcess = elementRegistry.get('AdHocSubProcess_1');
var oldMid = {
x: expandedSubProcess.x + expandedSubProcess.width / 2,
y: expandedSubProcess.y + expandedSubProcess.height / 2
};
// when
var collapsedSubProcess = bpmnReplace.replaceElement(expandedSubProcess,
{
type: 'bpmn:AdHocSubProcess',
isExpanded: false
}
);
// then
var newMid = {
x: collapsedSubProcess.x + collapsedSubProcess.width / 2,
y: collapsedSubProcess.y + collapsedSubProcess.height / 2
};
expect(newMid).to.eql(oldMid);
expect(collapsedSubProcess.width).to.be.at.least(defaultSize.width);
expect(collapsedSubProcess.height).to.be.at.least(defaultSize.height);
})
);
});
describe('undo', function() {
it('collapsed marker is removed',
inject(function(elementRegistry, bpmnReplace, commandStack) {
// given
var expandedSubProcess = elementRegistry.get('AdHocSubProcess_1');
var collapsedSubProcess = bpmnReplace.replaceElement(expandedSubProcess,
{
type: 'bpmn:AdHocSubProcess',
isExpanded: false
}
);
// when
commandStack.undo();
// then +-marker is placed
expect(getDi(collapsedSubProcess).isExpanded).to.eql(true);
})
);
it('originalBounds are restored',
inject(function(elementRegistry, bpmnReplace, commandStack) {
// given
var expandedSubProcess = elementRegistry.get('AdHocSubProcess_1');
var originalBounds = {
x: expandedSubProcess.x,
y: expandedSubProcess.y,
width: expandedSubProcess.width,
height: expandedSubProcess.height
};
bpmnReplace.replaceElement(expandedSubProcess,
{
type: 'bpmn:AdHocSubProcess',
isExpanded: false
}
);
// when
commandStack.undo();
// then
expect(expandedSubProcess).to.have.bounds(originalBounds);
})
);
it('show children that were visible',
inject(function(elementRegistry, bpmnReplace, commandStack) {
// given
var expandedSubProcess = elementRegistry.get('AdHocSubProcess_1');
var originalChildren = expandedSubProcess.children.slice();
bpmnReplace.replaceElement(expandedSubProcess,
{
type: 'bpmn:AdHocSubProcess',
isExpanded: false
}
);
// when
commandStack.undo();
// then keep children
originalChildren.forEach(function(c) {
expect(expandedSubProcess.children).to.include(c);
});
// and show the previously visible ones
expect(expandedSubProcess.children).to.satisfy(allShown());
})
);
});
});
describe('attaching marker', function() {
describe('collapsed', function() {
it('add ad-hoc-marker does not call toggleProvider',
inject(function(eventBus, bpmnReplace, elementRegistry) {
// given
var collapsedSubProcess = elementRegistry.get('SubProcess_3');
// should not be called
eventBus.once('commandStack.shape.toggleCollapse.execute', function(e) {
expect(true).to.eql(false);
});
// when
bpmnReplace.replaceElement(collapsedSubProcess,
{
type: 'bpmn:AdHocSubProcess',
isExpanded: false
}
);
// then
})
);
it('remove ad-hoc-marker does not call toggleProvider',
inject(function(eventBus, bpmnReplace, elementRegistry) {
// given
var collapsedSubProcess = elementRegistry.get('SubProcess_4');
// should not be called
eventBus.once('commandStack.shape.toggleCollapse.execute', function(e) {
expect(true).to.eql(false);
});
// when
bpmnReplace.replaceElement(collapsedSubProcess,
{
type: 'bpmn:SubProcess',
isExpanded: false
}
);
// then
})
);
});
describe('expanded', function() {
it('add ad-hoc-marker does not call toggleProvider',
inject(function(eventBus, bpmnReplace, elementRegistry) {
// given
var expandedSubProcess = elementRegistry.get('SubProcess_6');
// should not be called
eventBus.once('commandStack.shape.toggleCollapse.execute', function(e) {
expect(true).to.eql(false);
});
// when
bpmnReplace.replaceElement(expandedSubProcess,
{
type: 'bpmn:AdHocSubProcess',
isExpanded: true
}
);
// then
})
);
it('remove ad-hoc-marker does not call toggleProvider',
inject(function(eventBus, bpmnReplace, elementRegistry) {
// given
var expandedSubProcess = elementRegistry.get('AdHocSubProcess_1');
// should not be called
eventBus.once('commandStack.shape.toggleCollapse.execute', function(e) {
expect(true).to.eql(false);
});
// when
bpmnReplace.replaceElement(expandedSubProcess,
{
type: 'bpmn:SubProcess',
isExpanded: true
}
);
// then
})
);
});
});
});
// helpers //////////////////////
function allHidden() {
return childrenHidden(true);
}
function allShown() {
return childrenHidden(false);
}
function childrenHidden(hidden) {
return function(children) {
return children.every(function(child) {
// empty labels are allways hidden
if (child.type === 'label' && !child.businessObject.name) {
return child.hidden;
}
else {
return !!child.hidden == hidden;
}
});
};
}
================================================
FILE: test/spec/features/modeling/behavior/UnclaimIdBehaviorSpec.bpmn
================================================
SequenceFlow_1
SequenceFlow_1
================================================
FILE: test/spec/features/modeling/behavior/UnclaimIdBehaviorSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import coreModule from 'lib/core';
import modelingModule from 'lib/features/modeling';
describe('features/modeling - unclaim id', function() {
var testModules = [ coreModule, modelingModule ];
var diagramXML = require('./UnclaimIdBehaviorSpec.bpmn');
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
it('should unclaim ID of shape', inject(function(elementRegistry, moddle, modeling) {
// given
var startEvent = elementRegistry.get('StartEvent_1');
// when
modeling.removeElements([ startEvent ]);
// then
expect(moddle.ids.assigned('StartEvent_1')).to.be.false;
}));
it('should unclaim ID of process', inject(function(elementRegistry, moddle, modeling) {
// given
var participant = elementRegistry.get('Participant_1');
// when
modeling.removeElements([ participant ]);
// then
expect(moddle.ids.assigned('Process_1')).to.be.false;
}));
it('should unclaim ID of connection', inject(function(elementRegistry, moddle, modeling) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow_1');
// when
modeling.removeElements([ sequenceFlow ]);
// then
expect(moddle.ids.assigned('SequenceFlow_1')).to.be.false;
}));
it('should unclaim ID of children', inject(function(elementRegistry, moddle, modeling) {
// given
var participant = elementRegistry.get('Participant_1');
// when
modeling.removeElements([ participant ]);
// then
expect(moddle.ids.assigned('StartEvent_1')).to.be.false;
expect(moddle.ids.assigned('SequenceFlow_1')).to.be.false;
expect(moddle.ids.assigned('EndEvent_1')).to.be.false;
}));
it('should unclaim ID of root', inject(function(elementRegistry, moddle, modeling) {
// given
var participant = elementRegistry.get('Participant_1');
// when
modeling.removeElements([ participant ]);
// then
expect(moddle.ids.assigned('Collaboration_1')).to.be.false;
}));
describe('morphing', function() {
var simpleXML = require('../../../../fixtures/bpmn/simple.bpmn');
beforeEach(bootstrapModeler(simpleXML, { modules: testModules }));
it('should keep ID of root', inject(function(moddle, modeling) {
// when
modeling.makeCollaboration();
// then
expect(moddle.ids.assigned('Process_1')).to.exist;
}));
});
});
================================================
FILE: test/spec/features/modeling/behavior/UnsetDefaultFlowBehaviorSpec.bpmn
================================================
flow-default
flow-normal
flow-default
flow-normal
================================================
FILE: test/spec/features/modeling/behavior/UnsetDefaultFlowBehaviorSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import modelingModule from 'lib/features/modeling';
import coreModule from 'lib/core';
describe('features/modeling - delete default connection', function() {
var testModules = [ coreModule, modelingModule ];
var processDiagramXML = require('./UnsetDefaultFlowBehaviorSpec.bpmn');
beforeEach(bootstrapModeler(processDiagramXML, { modules: testModules }));
var gateway,
defaultConnection,
normalConnection;
beforeEach(inject(function(elementRegistry) {
gateway = elementRegistry.get('exclusive-gateway');
defaultConnection = elementRegistry.get('flow-default');
normalConnection = elementRegistry.get('flow-normal');
}));
it('should remove default connection', inject(function(modeling) {
// when
modeling.removeConnection(defaultConnection);
// then
expect(defaultConnection.parent).to.be.null;
expect(gateway.businessObject.default).to.be.null; // .property('default');
}));
it('should revert default connection', inject(function(modeling, commandStack) {
// given
modeling.removeConnection(defaultConnection);
// when
commandStack.undo();
// then
expect(defaultConnection.parent).to.be.not.null;
expect(gateway.businessObject.default).to.eql(defaultConnection.businessObject);
}));
it('should NOT remove default connection on removing other connections', inject(function(modeling) {
// when
modeling.removeConnection(normalConnection);
// then
expect(normalConnection.parent).to.be.null;
expect(defaultConnection.parent).to.be.not.null;
expect(gateway.businessObject.default).to.eql(defaultConnection.businessObject);
}));
it('should NOT remove default connection on restoring other connections', inject(function(modeling, commandStack) {
// given
modeling.removeConnection(normalConnection);
// when
commandStack.undo();
// then
expect(normalConnection.parent).to.be.not.null;
expect(defaultConnection.parent).to.be.not.null;
expect(gateway.businessObject.default).to.eql(defaultConnection.businessObject);
}));
});
================================================
FILE: test/spec/features/modeling/behavior/util/GeometricUtilSpec.js
================================================
import {
getDistancePointLine,
getAngle,
getDistancePointPoint,
perpendicularFoot,
rotateVector
} from 'lib/features/modeling/behavior/util/GeometricUtil';
describe('modeling/behavior/util - GeometricUtil', function() {
it('should re-export diagram-js utility', function() {
expect(getDistancePointLine).to.exist;
expect(getAngle).to.exist;
expect(getDistancePointPoint).to.exist;
expect(perpendicularFoot).to.exist;
expect(rotateVector).to.exist;
});
});
================================================
FILE: test/spec/features/modeling/behavior/util/LabelLayoutUtilSpec.js
================================================
var getLabelAdjustment = require('lib/features/modeling/behavior/util/LabelLayoutUtil').getLabelAdjustment;
describe('modeling/behavior/util - LabelLayoutUtil#getLabelAdjustment', function() {
describe('should recognize on the line label', function() {
var newLine = [
{ x: 10, y: 10 },
// -
{ x: 15, y: 10 },
// |
{ x: 15, y: 5 },
// -
{ x: 30, y: 5 }
];
it('horizontal', function() {
// given
var line = [
{ x: 10, y: 10 },
// -
{ x: 20, y: 10 }
];
// label with center { x: 5, y: 10 }
var label = {
x: 0,
y: 5,
width: 10,
height: 10
};
// when
var delta = getLabelAdjustment(label, newLine, line, { connectionStart: true });
// then
expect(delta).to.eql({ x: 0, y: 0 });
});
it('zero-length line', function() {
// given
var line = [
{ x: 10, y: 10 },
// -
{ x: 10, y: 10 }
];
// label with center { x: 5, y: 10 }
var label = {
x: 0,
y: 5,
width: 10,
height: 10
};
// when
var delta = getLabelAdjustment(label, newLine, line, { connectionStart: true });
// then
expect(delta).to.eql({ x: 0, y: 0 });
});
});
});
================================================
FILE: test/spec/features/modeling/behavior/util/LineAttachmentUtilSpec.js
================================================
var getAttachment = require('lib/features/modeling/behavior/util/LineAttachmentUtil').getAttachment;
var EPSILON = 0.1;
describe('modeling/behavior/util - LineAttachmentUtil#getAttachment', function() {
// test line
//
// *--*
// |
// *
// \
// *
//
var line = [
{ x: 10, y: 10 },
// -
{ x: 30, y: 10 },
// |
{ x: 30, y: 30 },
// \
{ x: 130, y: 130 }
];
describe('should recognize segment', function() {
it('horizontal', function() {
// when
var attachment = getAttachment({ x: 20, y: 5 }, line);
// then
expect(attachment).to.eql({
type: 'segment',
position: { x: 20, y: 10 },
segmentIndex: 0,
relativeLocation: 0.5
});
});
it('horizontal (on line)', function() {
// when
var attachment = getAttachment({ x: 20, y: 10 }, line);
// then
expect(attachment).to.eql({
type: 'segment',
position: { x: 20, y: 10 },
segmentIndex: 0,
relativeLocation: 0.5
});
});
it('vertical', function() {
// when
var attachment = getAttachment({ x: 40, y: 20 }, line);
// then
expect(attachment).to.eql({
type: 'segment',
position: { x: 30, y: 20 },
segmentIndex: 1,
relativeLocation: 0.5
});
});
it('diagonal', function() {
// when
var attachment = getAttachment({ x: 30, y: 40 }, line);
// then
expect(attachment).to.eql({
type: 'segment',
position: { x: 35, y: 35 },
segmentIndex: 2,
relativeLocation: 0.05
});
});
it('diagonal (on line)', function() {
// when
var attachment = getAttachment({ x: 50, y: 50 }, line);
// then
expect(attachment).to.eql({
type: 'segment',
position: { x: 50, y: 50 },
segmentIndex: 2,
relativeLocation: 0.2
});
});
it('horizontal (conflicting with vertical)', function() {
// when
var attachment = getAttachment({ x: 25, y: 15 }, line);
// then
expect(attachment).to.eql({
type: 'segment',
position: { x: 25, y: 10 },
segmentIndex: 0,
relativeLocation: 0.75
});
});
});
describe('should recognize bendpoint', function() {
it('horizontal', function() {
// when
var attachment = getAttachment({ x: 35, y: 5 }, line);
// then
expect(attachment).to.eql({
type: 'bendpoint',
position: { x: 30, y: 10 },
bendpointIndex: 1,
segmentIndex: 0
});
});
it('horizontal (segment start)', function() {
// when
var attachment = getAttachment({ x: 5, y: 5 }, line);
// then
expect(attachment).to.eql({
type: 'bendpoint',
position: { x: 10, y: 10 },
bendpointIndex: 0,
segmentIndex: 0
});
});
it('vertical', function() {
// when
var attachment = getAttachment({ x: 35, y: 10 }, line);
// then
expect(attachment).to.eql({
type: 'bendpoint',
position: { x: 30, y: 10 },
bendpointIndex: 1,
segmentIndex: 0
});
});
it('vertical (segment start)', function() {
var otherLine = [
{ x: 10, y: 10 },
{ x: 10, y: 50 }
];
// when
var attachment = getAttachment({ x: 5, y: 5 }, otherLine);
// then
expect(attachment).to.eql({
type: 'bendpoint',
position: { x: 10, y: 10 },
bendpointIndex: 0,
segmentIndex: 0
});
});
});
describe('should handle float values', function() {
// test line
//
// *--*
// |
// *
// \
// *
//
var floatingPointLine = [
{ x: 10.141592, y: 10.653589 },
// -
{ x: 30.793238, y: 10.462643 },
// |
{ x: 30.383279, y: 30.502884 },
// \
{ x: 130.197169, y: 130.399375 }
];
it('float value segment', function() {
// when
var attachment = getAttachment({ x: 20.197169, y: 5.399375 }, floatingPointLine);
// then
expect(attachment.type).to.equal('segment');
expect(attachment.segmentIndex).to.equal(0);
// expect values to be roughly equal
expect(attachment.relativeLocation).to.be.within(0.5 - EPSILON, 0.5 + EPSILON);
expect(attachment.position.x).to.be.within(20.25 - EPSILON, 20.25 + EPSILON);
expect(attachment.position.y).to.be.within(10.5 - EPSILON, 10.5 + EPSILON);
});
it('float value bendboint', function() {
// when
var attachment = getAttachment({ x: 35.197169, y: 5.399375 }, floatingPointLine);
// then
expect(attachment.type).to.equal('bendpoint');
expect(attachment.segmentIndex).to.equal(1);
expect(attachment.bendpointIndex).to.equal(1);
// expect values to be roughly equal
expect(attachment.position.x).to.be.within(30.793 - EPSILON, 30.793 + EPSILON);
expect(attachment.position.y).to.be.within(10.463 - EPSILON, 10.463 + EPSILON);
});
});
describe('handle zero-length line', function() {
var zeroLengthLine = [
{ x: 10, y: 10 },
// -
{ x: 10, y: 10 }
];
var zeroLengthFloatingPointLine = [
{ x: 10.1, y: 10.12313 },
// -
{ x: 10, y: 10.112 }
];
it('should treat zero-length as bendpoint attach', function() {
// when
var attachment = getAttachment({ x: 15.1, y: 15.123 }, zeroLengthLine);
// then
expect(attachment).to.eql({
type: 'bendpoint',
position: { x: 10, y: 10 },
segmentIndex: 0,
bendpointIndex: 0
});
});
it('should treat approx zero-length as bendpoint attach', function() {
// when
var attachment = getAttachment({ x: 15.1, y: 15.123 }, zeroLengthFloatingPointLine);
// then
expect(attachment).to.eql({
type: 'bendpoint',
position: { x: 10.1, y: 10.12313 },
segmentIndex: 0,
bendpointIndex: 0
});
});
});
});
================================================
FILE: test/spec/features/modeling/behavior/util/LineIntersectSpec.js
================================================
import intersection from 'lib/features/modeling/behavior/util/LineIntersect';
describe('modeling/behavior/util - LineIntersect', function() {
it('should compute intersections', function() {
expect(intersection(
{ x: 10, y: 20 }, { x: 50, y: 50 },
{ x: 10, y: 50 }, { x: 50, y: 50 }
)).to.eql({ x: 50, y: 50 });
expect(intersection(
{ x: 10, y: 20 }, { x: 10, y: 50 },
{ x: 10, y: 50 }, { x: 50, y: 50 }
)).to.eql({ x: 10, y: 50 });
expect(intersection(
{ x: 50, y: 50 }, { x: 10, y: 40 },
{ x: 10, y: 50 }, { x: 50, y: 50 }
)).to.eql({ x: 50, y: 50 });
expect(intersection(
{ x: 0, y: 0 }, { x: 100, y: 100 },
{ x: 40, y: 0 }, { x: 30, y: 10 }
)).to.eql({ x: 20, y: 20 });
});
});
================================================
FILE: test/spec/features/modeling/behavior/util/ResizeUtilSpec.js
================================================
import {
getParticipantResizeConstraints
} from 'lib/features/modeling/behavior/util/ResizeUtil';
describe('modeling/behavior/util - Resize', function() {
describe('#getParticipantResizeConstraints', function() {
it('should expose', function() {
expect(getParticipantResizeConstraints).to.exist;
});
});
});
================================================
FILE: test/spec/features/modeling/input-output/DataInputOutput.bpmn
================================================
DataInput
DataOutput
_9628422b-85a6-4857-8c14-7289b9fd9a8a
_29b8c649-e2a0-4dd3-804b-567e8cc71718
DataInput
_9628422b-85a6-4857-8c14-7289b9fd9a8a
_29b8c649-e2a0-4dd3-804b-567e8cc71718
DataOutput
================================================
FILE: test/spec/features/modeling/lanes/AddLaneSpec.js
================================================
import {
bootstrapModeler,
inject,
getBpmnJS
} from 'test/TestHelper';
import {
map,
pick
} from 'min-dash';
import contextPadModule from 'lib/features/context-pad';
import coreModule from 'lib/core';
import modelingModule from 'lib/features/modeling';
import {
getChildLanes
} from 'lib/features/modeling/util/LaneUtil';
import { query as domQuery } from 'min-dom';
var DEFAULT_LANE_HEIGHT = 120,
DEFAULT_VERTICAL_LANE_WIDTH = 120;
var testModules = [ coreModule, modelingModule ];
function getBounds(element) {
return pick(element, [ 'x', 'y', 'width', 'height' ]);
}
describe('features/modeling - add Lane', function() {
describe('nested Lanes', function() {
var diagramXML = require('./lanes.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
it('should add after Lane', inject(function(elementRegistry, modeling) {
// given
var laneShape = elementRegistry.get('Lane_A'),
belowLaneShape = elementRegistry.get('Lane_B');
// when
var newLane = modeling.addLane(laneShape, 'bottom');
// then
expect(newLane).to.have.bounds({
x: laneShape.x,
y: laneShape.y + laneShape.height,
width: laneShape.width,
height: DEFAULT_LANE_HEIGHT
});
// below lanes got moved by { dy: + LANE_HEIGHT }
expect(belowLaneShape).to.have.bounds({
x: laneShape.x,
y: laneShape.y + laneShape.height + DEFAULT_LANE_HEIGHT - 1,
width: laneShape.width,
height: belowLaneShape.height
});
expect(laneShape.di.isHorizontal).to.be.true;
expect(belowLaneShape.di.isHorizontal).to.be.true;
expect(newLane.di.isHorizontal).to.be.true;
}));
it('should add before Lane', inject(function(elementRegistry, modeling) {
// given
var laneShape = elementRegistry.get('Lane_B'),
aboveLaneShape = elementRegistry.get('Lane_A');
// when
var newLane = modeling.addLane(laneShape, 'top');
// then
expect(newLane).to.have.bounds({
x: laneShape.x,
y: laneShape.y - DEFAULT_LANE_HEIGHT,
width: laneShape.width,
height: DEFAULT_LANE_HEIGHT
});
// below lanes got moved by { dy: + LANE_HEIGHT }
expect(aboveLaneShape).to.have.bounds({
x: laneShape.x,
y: laneShape.y - aboveLaneShape.height - DEFAULT_LANE_HEIGHT + 1,
width: laneShape.width,
height: aboveLaneShape.height
});
expect(laneShape.di.isHorizontal).to.be.true;
expect(aboveLaneShape.di.isHorizontal).to.be.true;
expect(newLane.di.isHorizontal).to.be.true;
}));
it('should add horizontal Lane after', inject(function(elementRegistry, modeling) {
// given
var laneShape = elementRegistry.get('Lane_A'),
belowLaneShape = elementRegistry.get('Lane_B');
// when
var newLane = modeling.addLane(laneShape, 'right');
// then
expect(laneShape.di.isHorizontal).to.be.true;
expect(belowLaneShape.di.isHorizontal).to.be.true;
expect(newLane.di.isHorizontal).to.be.true;
}));
it('should add horizontal Lane before', inject(function(elementRegistry, modeling) {
// given
var laneShape = elementRegistry.get('Lane_B'),
aboveLaneShape = elementRegistry.get('Lane_A');
// when
var newLane = modeling.addLane(laneShape, 'left');
// then
expect(laneShape.di.isHorizontal).to.be.true;
expect(aboveLaneShape.di.isHorizontal).to.be.true;
expect(newLane.di.isHorizontal).to.be.true;
}));
it('should add before nested Lane', inject(function(elementRegistry, modeling) {
// given
var laneShape = elementRegistry.get('Nested_Lane_A'),
participantShape = elementRegistry.get('Participant_Lane'),
participantBounds = getBounds(participantShape);
// when
var newLane = modeling.addLane(laneShape, 'top');
// then
expect(newLane).to.have.bounds({
x: laneShape.x,
y: laneShape.y - DEFAULT_LANE_HEIGHT,
width: laneShape.width,
height: DEFAULT_LANE_HEIGHT
});
// participant got enlarged { top: + LANE_HEIGHT }
expect(participantShape).to.have.bounds({
x: participantBounds.x,
y: participantBounds.y - newLane.height,
width: participantBounds.width,
height: participantBounds.height + newLane.height
});
expect(participantShape.di.isHorizontal).to.be.true;
expect(laneShape.di.isHorizontal).to.be.true;
expect(newLane.di.isHorizontal).to.be.true;
}));
it('should add after Participant', inject(function(elementRegistry, modeling) {
// given
var participantShape = elementRegistry.get('Participant_Lane'),
participantBounds = getBounds(participantShape),
lastLaneShape = elementRegistry.get('Lane_B'),
lastLaneBounds = getBounds(lastLaneShape);
// when
var newLane = modeling.addLane(participantShape, 'bottom');
// then
expect(newLane).to.have.bounds({
x: participantBounds.x + 30,
y: participantBounds.y + participantBounds.height,
width: participantBounds.width - 30,
height: DEFAULT_LANE_HEIGHT
});
// last lane kept position
expect(lastLaneShape).to.have.bounds(lastLaneBounds);
// participant got enlarged by { dy: + LANE_HEIGHT } at bottom
expect(participantShape).to.have.bounds({
x: participantBounds.x,
y: participantBounds.y,
width: participantBounds.width,
height: participantBounds.height + DEFAULT_LANE_HEIGHT
});
expect(participantShape.di.isHorizontal).to.be.true;
expect(lastLaneShape.di.isHorizontal).to.be.true;
expect(newLane.di.isHorizontal).to.be.true;
}));
it('should add before Participant', inject(function(elementRegistry, modeling) {
// given
var participantShape = elementRegistry.get('Participant_Lane'),
participantBounds = getBounds(participantShape),
firstLaneShape = elementRegistry.get('Lane_A'),
firstLaneBounds = getBounds(firstLaneShape);
// when
var newLane = modeling.addLane(participantShape, 'top');
// then
expect(newLane).to.have.bounds({
x: participantBounds.x + 30,
y: participantBounds.y - DEFAULT_LANE_HEIGHT,
width: participantBounds.width - 30,
height: DEFAULT_LANE_HEIGHT
});
// first lane kept position
expect(firstLaneShape).to.have.bounds(firstLaneBounds);
// participant got enlarged by { dy: + LANE_HEIGHT } at bottom
expect(participantShape).to.have.bounds({
x: participantBounds.x,
y: participantBounds.y - DEFAULT_LANE_HEIGHT,
width: participantBounds.width,
height: participantBounds.height + DEFAULT_LANE_HEIGHT
});
expect(participantShape.di.isHorizontal).to.be.true;
expect(firstLaneShape.di.isHorizontal).to.be.true;
expect(newLane.di.isHorizontal).to.be.true;
}));
});
describe('nested vertical Lanes', function() {
var diagramXML = require('./lanes.vertical.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
it('should add after Lane', inject(function(elementRegistry, modeling) {
// given
var laneShape = elementRegistry.get('Vertical_Lane_A'),
rightLaneShape = elementRegistry.get('Vertical_Lane_B');
// when
var newLane = modeling.addLane(laneShape, 'right');
// then
expect(newLane).to.have.bounds({
x: laneShape.x + laneShape.width,
y: laneShape.y,
height: laneShape.height,
width: DEFAULT_VERTICAL_LANE_WIDTH
});
// right lanes got moved by { dx: + DEFAULT_VERTICAL_LANE_WIDTH }
expect(rightLaneShape).to.have.bounds({
x: laneShape.x + laneShape.width + DEFAULT_VERTICAL_LANE_WIDTH,
y: laneShape.y,
width: rightLaneShape.width,
height: laneShape.height
});
expect(laneShape.di.isHorizontal).to.be.false;
expect(rightLaneShape.di.isHorizontal).to.be.false;
expect(newLane.di.isHorizontal).to.be.false;
}));
it('should add before Lane', inject(function(elementRegistry, modeling) {
// given
var laneShape = elementRegistry.get('Vertical_Lane_B'),
leftLaneShape = elementRegistry.get('Vertical_Lane_A');
// when
var newLane = modeling.addLane(laneShape, 'left');
// then
expect(newLane).to.have.bounds({
x: laneShape.x - DEFAULT_VERTICAL_LANE_WIDTH,
y: laneShape.y,
width: DEFAULT_VERTICAL_LANE_WIDTH,
height: laneShape.height
});
// right lanes got moved by { dx: + DEFAULT_VERTICAL_LANE_WIDTH }
expect(leftLaneShape).to.have.bounds({
x: laneShape.x - leftLaneShape.width - DEFAULT_VERTICAL_LANE_WIDTH,
y: laneShape.y,
width: leftLaneShape.width,
height: laneShape.height
});
expect(laneShape.di.isHorizontal).to.be.false;
expect(leftLaneShape.di.isHorizontal).to.be.false;
expect(newLane.di.isHorizontal).to.be.false;
}));
it('should add vertical Lane after', inject(function(elementRegistry, modeling) {
// given
var laneShape = elementRegistry.get('Vertical_Lane_A'),
rightLaneShape = elementRegistry.get('Vertical_Lane_B');
// when
var newLane = modeling.addLane(laneShape, 'bottom');
// then
expect(laneShape.di.isHorizontal).to.be.false;
expect(rightLaneShape.di.isHorizontal).to.be.false;
expect(newLane.di.isHorizontal).to.be.false;
}));
it('should add vertical Lane before', inject(function(elementRegistry, modeling) {
// given
var laneShape = elementRegistry.get('Vertical_Lane_B'),
leftLaneShape = elementRegistry.get('Vertical_Lane_A');
// when
var newLane = modeling.addLane(laneShape, 'top');
// then
expect(laneShape.di.isHorizontal).to.be.false;
expect(leftLaneShape.di.isHorizontal).to.be.false;
expect(newLane.di.isHorizontal).to.be.false;
}));
it('should add before nested Lane', inject(function(elementRegistry, modeling) {
// given
var laneShape = elementRegistry.get('Nested_Vertical_Lane_A'),
participantShape = elementRegistry.get('Vertical_Participant_Lane'),
participantBounds = getBounds(participantShape);
// when
var newLane = modeling.addLane(laneShape, 'left');
// then
expect(newLane).to.have.bounds({
x: laneShape.x - DEFAULT_VERTICAL_LANE_WIDTH,
y: laneShape.y,
width: DEFAULT_VERTICAL_LANE_WIDTH,
height: laneShape.height
});
// participant got enlarged { left: + DEFAULT_VERTICAL_LANE_WIDTH }
expect(participantShape).to.have.bounds({
x: participantBounds.x - newLane.width,
y: participantBounds.y,
width: participantBounds.width + newLane.width,
height: participantBounds.height
});
expect(laneShape.di.isHorizontal).to.be.false;
expect(participantShape.di.isHorizontal).to.be.false;
expect(newLane.di.isHorizontal).to.be.false;
}));
it('should add after Participant', inject(function(elementRegistry, modeling) {
// given
var participantShape = elementRegistry.get('Vertical_Participant_Lane'),
participantBounds = getBounds(participantShape),
lastLaneShape = elementRegistry.get('Vertical_Lane_B'),
lastLaneBounds = getBounds(lastLaneShape);
// when
var newLane = modeling.addLane(participantShape, 'right');
// then
expect(newLane).to.have.bounds({
x: participantBounds.x + participantBounds.width,
y: participantBounds.y + 30,
width: DEFAULT_VERTICAL_LANE_WIDTH,
height: participantBounds.height - 30
});
// last lane kept position
expect(lastLaneShape).to.have.bounds(lastLaneBounds);
// participant got enlarged by { dx: + DEFAULT_VERTICAL_LANE_WIDTH } to the right
expect(participantShape).to.have.bounds({
x: participantBounds.x,
y: participantBounds.y,
width: participantBounds.width + DEFAULT_VERTICAL_LANE_WIDTH,
height: participantBounds.height
});
expect(participantShape.di.isHorizontal).to.be.false;
expect(lastLaneShape.di.isHorizontal).to.be.false;
expect(newLane.di.isHorizontal).to.be.false;
}));
it('should add before Participant', inject(function(elementRegistry, modeling) {
// given
var participantShape = elementRegistry.get('Vertical_Participant_Lane'),
participantBounds = getBounds(participantShape),
firstLaneShape = elementRegistry.get('Vertical_Lane_A'),
firstLaneBounds = getBounds(firstLaneShape);
// when
var newLane = modeling.addLane(participantShape, 'left');
// then
expect(newLane).to.have.bounds({
x: participantBounds.x - DEFAULT_VERTICAL_LANE_WIDTH,
y: participantBounds.y + 30,
width: DEFAULT_VERTICAL_LANE_WIDTH,
height: participantBounds.height - 30
});
// first lane kept position
expect(firstLaneShape).to.have.bounds(firstLaneBounds);
// participant got enlarged by { dx: + DEFAULT_VERTICAL_LANE_WIDTH } to the left
expect(participantShape).to.have.bounds({
x: participantBounds.x - DEFAULT_VERTICAL_LANE_WIDTH,
y: participantBounds.y,
width: participantBounds.width + DEFAULT_VERTICAL_LANE_WIDTH,
height: participantBounds.height
});
expect(participantShape.di.isHorizontal).to.be.false;
expect(firstLaneShape.di.isHorizontal).to.be.false;
expect(newLane.di.isHorizontal).to.be.false;
}));
});
describe('without Participant', function() {
var diagramXML = require('./lanes.only.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
it('should add horizontal Lane after', inject(function(elementRegistry, modeling) {
// given
var laneShape = elementRegistry.get('Lane_A'),
belowLaneShape = elementRegistry.get('Lane_B');
// when
var newLane = modeling.addLane(laneShape, 'right');
// then
expect(laneShape.di.isHorizontal).to.be.true;
expect(belowLaneShape.di.isHorizontal).to.be.true;
expect(newLane.di.isHorizontal).to.be.true;
}));
it('should add horizontal Lane before', inject(function(elementRegistry, modeling) {
// given
var laneShape = elementRegistry.get('Lane_B'),
aboveLaneShape = elementRegistry.get('Lane_A');
// when
var newLane = modeling.addLane(laneShape, 'left');
// then
expect(laneShape.di.isHorizontal).to.be.true;
expect(aboveLaneShape.di.isHorizontal).to.be.true;
expect(newLane.di.isHorizontal).to.be.true;
}));
});
describe('vertical without Participant', function() {
var diagramXML = require('./lanes.only.vertical.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
it('should add vertical Lane after', inject(function(elementRegistry, modeling) {
// given
var laneShape = elementRegistry.get('Vertical_Lane_A'),
rightLaneShape = elementRegistry.get('Vertical_Lane_B');
// when
var newLane = modeling.addLane(laneShape, 'bottom');
// then
expect(laneShape.di.isHorizontal).to.be.false;
expect(rightLaneShape.di.isHorizontal).to.be.false;
expect(newLane.di.isHorizontal).to.be.false;
}));
it('should add vertical Lane before', inject(function(elementRegistry, modeling) {
// given
var laneShape = elementRegistry.get('Vertical_Lane_B'),
leftLaneShape = elementRegistry.get('Vertical_Lane_A');
// when
var newLane = modeling.addLane(laneShape, 'top');
// then
expect(laneShape.di.isHorizontal).to.be.false;
expect(leftLaneShape.di.isHorizontal).to.be.false;
expect(newLane.di.isHorizontal).to.be.false;
}));
});
describe('Participant without Lane', function() {
var diagramXML = require('./participant-no-lane.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
it('should add after Participant', inject(function(elementRegistry, modeling) {
// given
var participantShape = elementRegistry.get('Participant_No_Lane'),
participantBounds = getBounds(participantShape);
// when
modeling.addLane(participantShape, 'bottom');
var childLanes = getChildLanes(participantShape);
// then
expect(childLanes.length).to.eql(2);
var firstLane = childLanes[0],
secondLane = childLanes[1];
// new lane was added at participant location
expect(firstLane).to.have.bounds({
x: participantBounds.x + 30,
y: participantBounds.y,
width: participantBounds.width - 30,
height: participantBounds.height
});
expect(secondLane).to.have.bounds({
x: participantBounds.x + 30,
y: participantBounds.y + participantBounds.height,
width: participantBounds.width - 30,
height: DEFAULT_LANE_HEIGHT
});
expect(firstLane.di.isHorizontal).to.be.true;
expect(secondLane.di.isHorizontal).to.be.true;
}));
it('should add before Participant', inject(function(elementRegistry, modeling) {
// given
var participantShape = elementRegistry.get('Participant_No_Lane'),
participantBounds = getBounds(participantShape);
// when
modeling.addLane(participantShape, 'top');
var childLanes = getChildLanes(participantShape);
// then
expect(childLanes.length).to.eql(2);
var firstLane = childLanes[0],
secondLane = childLanes[1];
// new lane was added at participant location
expect(firstLane).to.have.bounds({
x: participantBounds.x + 30,
y: participantBounds.y,
width: participantBounds.width - 30,
height: participantBounds.height
});
expect(secondLane).to.have.bounds({
x: participantBounds.x + 30,
y: participantBounds.y - DEFAULT_LANE_HEIGHT,
width: participantBounds.width - 30,
height: DEFAULT_LANE_HEIGHT
});
expect(firstLane.di.isHorizontal).to.be.true;
expect(secondLane.di.isHorizontal).to.be.true;
}));
});
describe('vertical Participant without Lane', function() {
var diagramXML = require('./participant-no-lane-vertical.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
it('should add after Participant', inject(function(elementRegistry, modeling) {
// given
var participantShape = elementRegistry.get('Vertical_Participant_No_Lane'),
participantBounds = getBounds(participantShape);
// when
modeling.addLane(participantShape, 'right');
var childLanes = getChildLanes(participantShape);
// then
expect(childLanes.length).to.eql(2);
var firstLane = childLanes[0],
secondLane = childLanes[1];
// new lane was added at participant location
expect(firstLane).to.have.bounds({
x: participantBounds.x,
y: participantBounds.y + 30,
width: participantBounds.width,
height: participantBounds.height - 30
});
expect(secondLane).to.have.bounds({
x: participantBounds.x + participantBounds.width,
y: participantBounds.y + 30,
width: DEFAULT_VERTICAL_LANE_WIDTH,
height: participantBounds.height - 30
});
expect(firstLane.di.isHorizontal).to.be.false;
expect(secondLane.di.isHorizontal).to.be.false;
}));
it('should add before Participant', inject(function(elementRegistry, modeling) {
// given
var participantShape = elementRegistry.get('Vertical_Participant_No_Lane'),
participantBounds = getBounds(participantShape);
// when
modeling.addLane(participantShape, 'left');
var childLanes = getChildLanes(participantShape);
// then
expect(childLanes.length).to.eql(2);
var firstLane = childLanes[0],
secondLane = childLanes[1];
// new lane was added at participant location
expect(firstLane).to.have.bounds({
x: participantBounds.x,
y: participantBounds.y + 30,
width: participantBounds.width,
height: participantBounds.height - 30
});
expect(secondLane).to.have.bounds({
x: participantBounds.x - DEFAULT_VERTICAL_LANE_WIDTH,
y: participantBounds.y + 30,
width: DEFAULT_VERTICAL_LANE_WIDTH,
height: participantBounds.height - 30
});
expect(firstLane.di.isHorizontal).to.be.false;
expect(secondLane.di.isHorizontal).to.be.false;
}));
});
describe('flow node handling - basics', function() {
var diagramXML = require('./lanes.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
it('should move up flow nodes and sequence flows', inject(function(elementRegistry, modeling) {
// given
var laneShape = elementRegistry.get('Nested_Lane_B'),
task_Boundary = elementRegistry.get('Task_Boundary'),
boundary = elementRegistry.get('Boundary'),
sequenceFlow = elementRegistry.get('SequenceFlow'),
sequenceFlow_From_Boundary = elementRegistry.get('SequenceFlow_From_Boundary');
// when
var newLane = modeling.addLane(laneShape, 'top');
// then
expect(task_Boundary).to.have.position({ x: 264, y: -57 });
expect(boundary).to.have.position({ x: 311, y: 5 });
expect(sequenceFlow_From_Boundary).to.have.waypoints([
{ x: 329, y: 161 - newLane.height },
{ x: 329, y: 188 - newLane.height },
{ x: 482, y: 188 - newLane.height },
{ x: 482, y: 143 - newLane.height }
]);
expect(sequenceFlow).to.have.waypoints([
{ x: 364, y: 103 - newLane.height },
{ x: 432, y: 103 - newLane.height }
]);
}));
});
describe('flow node handling - basics vertical', function() {
var diagramXML = require('./lanes.vertical.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
it('should move left flow nodes and sequence flows', inject(function(elementRegistry, modeling) {
// given
var laneShape = elementRegistry.get('Nested_Vertical_Lane_B'),
task_Boundary = elementRegistry.get('V_Task_Boundary'),
boundary = elementRegistry.get('V_Boundary'),
sequenceFlow = elementRegistry.get('Flow_V'),
sequenceFlow_From_Boundary = elementRegistry.get('Flow_From_V_Boundary');
// when
var newLane = modeling.addLane(laneShape, 'left');
// then
expect(task_Boundary).to.have.position({ x: 190 - newLane.width, y: 170 });
expect(boundary).to.have.position({ x: 272 - newLane.width, y: 212 });
expect(sequenceFlow_From_Boundary).to.have.waypoints([
{ x: 308 - newLane.width, y: 230 },
{ x: 320 - newLane.width, y: 230 },
{ x: 320 - newLane.width, y: 370 },
{ x: 290 - newLane.width, y: 370 }
]);
expect(sequenceFlow).to.have.waypoints([
{ x: 240 - newLane.width, y: 250 },
{ x: 240 - newLane.width, y: 330 }
]);
}));
});
describe('flow node handling', function() {
var diagramXML = require('./lanes-flow-nodes.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
function addLaneAbove(laneId) {
return getBpmnJS().invoke(function(elementRegistry, modeling) {
var existingLane = elementRegistry.get(laneId);
expect(existingLane).to.exist;
return modeling.addLane(existingLane, 'top');
});
}
function addLaneBelow(laneId) {
return getBpmnJS().invoke(function(elementRegistry, modeling) {
var existingLane = elementRegistry.get(laneId);
expect(existingLane).to.exist;
return modeling.addLane(existingLane, 'bottom');
});
}
it('should move flow nodes', inject(function(elementRegistry, modeling) {
// given
var task_Boundary = elementRegistry.get('Task_Boundary'),
taskPosition = getPosition(task_Boundary),
boundary = elementRegistry.get('Boundary'),
boundaryPosition = getPosition(boundary);
// when
addLaneAbove('Nested_Lane_B');
// then
expect(task_Boundary).to.have.position({ x: taskPosition.x, y: taskPosition.y - 120 });
expect(boundary).to.have.position({ x: boundaryPosition.x, y: boundaryPosition.y - 120 });
}));
it('should move sequence flows', inject(function(elementRegistry, modeling) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow'),
sequenceFlowWaypoints = sequenceFlow.waypoints,
sequenceFlow_From_Boundary = elementRegistry.get('SequenceFlow_From_Boundary'),
sequenceFlow_From_BoundaryWaypoints = sequenceFlow_From_Boundary.waypoints;
// when
addLaneAbove('Nested_Lane_B');
// then
expect(sequenceFlow_From_Boundary).to.have.waypoints(
moveWaypoints(sequenceFlow_From_BoundaryWaypoints, 0, -120)
);
expect(sequenceFlow).to.have.waypoints(
moveWaypoints(sequenceFlowWaypoints, 0, -120)
);
}));
it('should move message flows when lane added above', inject(function(elementRegistry) {
// given
var messageFlow = elementRegistry.get('MessageFlowAbove'),
messageFlowWaypoints = messageFlow.waypoints;
// when
addLaneAbove('Nested_Lane_B');
// then
expect(messageFlow).to.have.waypoints([
movePosition(messageFlowWaypoints[0], 0, -120),
messageFlowWaypoints[1]
]);
}));
it('should move message flows when lane added below', inject(function(elementRegistry) {
// given
var messageFlow = elementRegistry.get('MessageFlowBelow'),
messageFlowWaypoints = messageFlow.waypoints;
// when
addLaneBelow('Nested_Lane_B');
// then
expect(messageFlow).to.have.waypoints([
messageFlowWaypoints[0],
movePosition(messageFlowWaypoints[1], 0, 120)
]);
}));
it('should move external labels', inject(function(elementRegistry, modeling) {
// given
var event = elementRegistry.get('Event'),
label = event.label,
labelPosition = getPosition(label),
boundary = elementRegistry.get('Boundary'),
boundaryLabel = boundary.label,
boundaryLabelPosition = getPosition(boundaryLabel);
// TODO(nikku): consolidate import + editing behavior => not consistent right now
// when
// force move label to trigger label editing + update parent behavior
modeling.moveElements([ label ], { x: 0, y: 0 });
addLaneAbove('Nested_Lane_B');
// then
expect(label).to.have.position({
x: labelPosition.x,
y: labelPosition.y - 120
});
expect(boundaryLabel).to.have.position({
x: boundaryLabelPosition.x,
y: boundaryLabelPosition.y - 120
});
}));
});
describe('flow node handling - vertical', function() {
var diagramXML = require('./lanes-flow-nodes-vertical.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
function addLaneLeft(laneId) {
return getBpmnJS().invoke(function(elementRegistry, modeling) {
var existingLane = elementRegistry.get(laneId);
expect(existingLane).to.exist;
return modeling.addLane(existingLane, 'left');
});
}
function addLaneRight(laneId) {
return getBpmnJS().invoke(function(elementRegistry, modeling) {
var existingLane = elementRegistry.get(laneId);
expect(existingLane).to.exist;
return modeling.addLane(existingLane, 'right');
});
}
it('should move flow nodes', inject(function(elementRegistry, modeling) {
// given
var task_Boundary = elementRegistry.get('V_Task_Boundary'),
taskPosition = getPosition(task_Boundary),
boundary = elementRegistry.get('V_Boundary'),
boundaryPosition = getPosition(boundary);
// when
addLaneLeft('Nested_Vertical_Lane_B');
// then
expect(task_Boundary).to.have.position({ x: taskPosition.x - 120, y: taskPosition.y });
expect(boundary).to.have.position({ x: boundaryPosition.x - 120, y: boundaryPosition.y });
}));
it('should move sequence flows', inject(function(elementRegistry, modeling) {
// given
var sequenceFlow = elementRegistry.get('Flow_V'),
sequenceFlowWaypoints = sequenceFlow.waypoints,
sequenceFlow_From_Boundary = elementRegistry.get('Flow_From_V_Boundary'),
sequenceFlow_From_BoundaryWaypoints = sequenceFlow_From_Boundary.waypoints;
// when
addLaneLeft('Nested_Vertical_Lane_B');
// then
expect(sequenceFlow_From_Boundary).to.have.waypoints(
moveWaypoints(sequenceFlow_From_BoundaryWaypoints, -120, 0)
);
expect(sequenceFlow).to.have.waypoints(
moveWaypoints(sequenceFlowWaypoints, -120, 0)
);
}));
it('should move message flows when lane added above', inject(function(elementRegistry) {
// given
var messageFlow = elementRegistry.get('MessageFlowLeft'),
messageFlowWaypoints = messageFlow.waypoints;
// when
addLaneLeft('Nested_Vertical_Lane_B');
// then
expect(messageFlow).to.have.waypoints([
movePosition(messageFlowWaypoints[0], -120, 0),
messageFlowWaypoints[1]
]);
}));
it('should move message flows when lane added below', inject(function(elementRegistry) {
// given
var messageFlow = elementRegistry.get('MessageFlowRight'),
messageFlowWaypoints = messageFlow.waypoints;
// when
addLaneRight('Nested_Vertical_Lane_B');
// then
expect(messageFlow).to.have.waypoints([
messageFlowWaypoints[0],
movePosition(messageFlowWaypoints[1], 120, 0)
]);
}));
it('should move external labels', inject(function(elementRegistry, modeling) {
// given
var event = elementRegistry.get('V_Event'),
label = event.label,
labelPosition = getPosition(label),
boundary = elementRegistry.get('V_Boundary'),
boundaryLabel = boundary.label,
boundaryLabelPosition = getPosition(boundaryLabel);
// TODO(nikku): consolidate import + editing behavior => not consistent right now
// when
// force move label to trigger label editing + update parent behavior
modeling.moveElements([ label ], { x: 0, y: 0 });
addLaneLeft('Nested_Vertical_Lane_B');
// then
expect(label).to.have.position({
x: labelPosition.x - 120,
y: labelPosition.y
});
expect(boundaryLabel).to.have.position({
x: boundaryLabelPosition.x - 120,
y: boundaryLabelPosition.y
});
}));
});
describe('Internet Explorer', function() {
var diagramXML = require('./participant-single-lane.bpmn');
var testModules = [
contextPadModule,
coreModule,
modelingModule
];
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
// must be executed manually, cannot be reproduced programmatically
// https://github.com/bpmn-io/bpmn-js/issues/746
it('should NOT blow up in Internet Explorer', inject(
function(contextPad, elementRegistry) {
// given
var lane = elementRegistry.get('Lane_1');
contextPad.open(lane);
// mock event
var event = padEvent('lane-insert-below');
// when
contextPad.trigger('click', event);
// then
// expect Internet Explorer NOT to blow up
}
));
});
});
// helpers //////////
function padEntry(element, name) {
return domQuery('[data-action="' + name + '"]', element);
}
function padEvent(entry) {
return getBpmnJS().invoke(function(overlays) {
var target = padEntry(overlays._overlayRoot, entry);
return {
target: target,
preventDefault: function() {},
clientX: 100,
clientY: 100
};
});
}
function getPosition(element) {
return {
x: element.x,
y: element.y
};
}
function moveWaypoints(waypoints, deltaX, deltaY) {
return map(waypoints, function(waypoint) {
return movePosition(waypoint, deltaX, deltaY);
});
}
function movePosition(point, deltaX, deltaY) {
return {
x: point.x + deltaX,
y: point.y + deltaY
};
}
================================================
FILE: test/spec/features/modeling/lanes/DeleteLaneSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import {
pick
} from 'min-dash';
import modelingModule from 'lib/features/modeling';
import coreModule from 'lib/core';
describe('features/modeling - delete lane', function() {
var diagramXML = require('./lanes.bpmn');
var testModules = [ coreModule, modelingModule ];
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
it('should remove first Lane', inject(function(elementRegistry, modeling) {
// given
var laneShape = elementRegistry.get('Lane_A'),
belowLaneShape = elementRegistry.get('Lane_B'),
belowLaneBounds = getBounds(belowLaneShape);
// when
modeling.removeShape(laneShape);
// then
expect(belowLaneShape).to.have.bounds({
x: belowLaneBounds.x,
y: belowLaneBounds.y - laneShape.height,
width: belowLaneBounds.width,
height: belowLaneBounds.height + laneShape.height
});
}));
it('should remove last Lane', inject(function(elementRegistry, modeling) {
// given
var laneShape = elementRegistry.get('Lane_B'),
aboveLaneShape = elementRegistry.get('Lane_A'),
aboveLaneBounds = getBounds(aboveLaneShape);
// when
modeling.removeShape(laneShape);
// then
expect(aboveLaneShape).to.have.bounds({
x: aboveLaneBounds.x,
y: aboveLaneBounds.y,
width: aboveLaneShape.width,
height: aboveLaneBounds.height + laneShape.height
});
}));
describe('three lanes', function() {
it('should remove middle Lane', inject(function(elementRegistry, modeling) {
// given
var laneShape = elementRegistry.get('Nested_Lane_B'),
aboveLaneShape = elementRegistry.get('Nested_Lane_A'),
aboveLaneBounds = getBounds(aboveLaneShape),
belowLaneShape = elementRegistry.get('Nested_Lane_C'),
belowLaneBounds = getBounds(belowLaneShape);
// when
modeling.removeShape(laneShape);
// then
expect(aboveLaneShape).to.have.bounds({
x: aboveLaneBounds.x,
y: aboveLaneBounds.y,
width: aboveLaneShape.width,
height: aboveLaneBounds.height + laneShape.height / 2
});
expect(belowLaneShape).to.have.bounds({
x: belowLaneBounds.x,
y: belowLaneBounds.y - laneShape.height / 2,
width: belowLaneBounds.width,
height: belowLaneBounds.height + laneShape.height / 2
});
}));
it('should remove first Lane', inject(function(elementRegistry, modeling) {
// given
var laneShape = elementRegistry.get('Nested_Lane_A'),
belowLaneShape = elementRegistry.get('Nested_Lane_B'),
belowLaneBounds = getBounds(belowLaneShape),
lastLaneShape = elementRegistry.get('Nested_Lane_C'),
lastLaneBounds = getBounds(lastLaneShape);
// when
modeling.removeShape(laneShape);
// then
expect(belowLaneShape).to.have.bounds({
x: belowLaneBounds.x,
y: belowLaneBounds.y - laneShape.height,
width: belowLaneBounds.width,
height: belowLaneBounds.height + laneShape.height
});
expect(lastLaneShape).to.have.bounds(lastLaneBounds);
}));
});
});
describe('features/modeling - delete vertical lane', function() {
var diagramXML = require('./lanes.vertical.bpmn');
var testModules = [ coreModule, modelingModule ];
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
it('should remove first Lane', inject(function(elementRegistry, modeling) {
// given
var laneShape = elementRegistry.get('Vertical_Lane_A'),
rightSideLaneShape = elementRegistry.get('Vertical_Lane_B'),
rightSideLaneBounds = getBounds(rightSideLaneShape);
// when
modeling.removeShape(laneShape);
// then
expect(rightSideLaneShape).to.have.bounds({
x: rightSideLaneBounds.x - laneShape.width,
y: rightSideLaneBounds.y,
width: rightSideLaneBounds.width + laneShape.width,
height: rightSideLaneBounds.height
});
}));
it('should remove last Lane', inject(function(elementRegistry, modeling) {
// given
var laneShape = elementRegistry.get('Vertical_Lane_B'),
leftSideLaneShape = elementRegistry.get('Vertical_Lane_A'),
leftSideLaneBounds = getBounds(leftSideLaneShape);
// when
modeling.removeShape(laneShape);
// then
expect(leftSideLaneShape).to.have.bounds({
x: leftSideLaneBounds.x,
y: leftSideLaneBounds.y,
width: leftSideLaneBounds.width + laneShape.width,
height: leftSideLaneBounds.height
});
}));
describe('three lanes', function() {
it('should remove middle Lane', inject(function(elementRegistry, modeling) {
// given
var laneShape = elementRegistry.get('Nested_Vertical_Lane_B'),
leftSideLaneShape = elementRegistry.get('Nested_Vertical_Lane_A'),
leftSideLaneBounds = getBounds(leftSideLaneShape),
rightSideLaneShape = elementRegistry.get('Nested_Vertical_Lane_C'),
rightSideLaneBounds = getBounds(rightSideLaneShape);
// when
modeling.removeShape(laneShape);
// then
expect(leftSideLaneShape).to.have.bounds({
x: leftSideLaneBounds.x,
y: leftSideLaneBounds.y,
width: leftSideLaneBounds.width + laneShape.width / 2,
height: leftSideLaneBounds.height
});
expect(rightSideLaneShape).to.have.bounds({
x: rightSideLaneBounds.x - laneShape.width / 2,
y: rightSideLaneBounds.y,
width: rightSideLaneBounds.width + laneShape.width / 2,
height: rightSideLaneBounds.height
});
}));
it('should remove first Lane', inject(function(elementRegistry, modeling) {
// given
var laneShape = elementRegistry.get('Nested_Vertical_Lane_A'),
rightSideLaneShape = elementRegistry.get('Nested_Vertical_Lane_B'),
rightSideLaneBounds = getBounds(rightSideLaneShape),
lastLaneShape = elementRegistry.get('Nested_Vertical_Lane_C'),
lastLaneBounds = getBounds(lastLaneShape);
// when
modeling.removeShape(laneShape);
// then
expect(rightSideLaneShape).to.have.bounds({
x: rightSideLaneBounds.x - laneShape.width,
y: rightSideLaneBounds.y,
width: rightSideLaneBounds.width + laneShape.width,
height: rightSideLaneBounds.height
});
expect(lastLaneShape).to.have.bounds(lastLaneBounds);
}));
});
});
// helpers ///////////////
function getBounds(element) {
return pick(element, [ 'x', 'y', 'width', 'height' ]);
}
================================================
FILE: test/spec/features/modeling/lanes/ResizeLaneSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import modelingModule from 'lib/features/modeling';
import coreModule from 'lib/core';
import {
resizeTRBL
} from 'diagram-js/lib/features/resize/ResizeUtil';
import {
pick
} from 'min-dash';
function getBounds(element) {
return pick(element, [ 'x', 'y', 'width', 'height' ]);
}
describe('features/modeling - resize lane', function() {
var diagramXML = require('./lanes.bpmn');
var testModules = [ coreModule, modelingModule ];
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
describe('vertical', function() {
describe('compensating', function() {
it('should expand Lane top', inject(function(elementRegistry, modeling) {
// given
var laneShape = elementRegistry.get('Lane_A'),
participantShape = elementRegistry.get('Participant_Lane');
var newLaneBounds = resizeTRBL(laneShape, { top: -50 });
var expectedParticipantBounds = resizeTRBL(participantShape, { top: -50 });
// when
modeling.resizeLane(laneShape, newLaneBounds, false);
// then
expect(laneShape).to.have.bounds(newLaneBounds);
expect(participantShape).to.have.bounds(expectedParticipantBounds);
}));
it('should shrink Lane top', inject(function(elementRegistry, modeling) {
// given
var laneShape = elementRegistry.get('Lane_A'),
participantShape = elementRegistry.get('Participant_Lane');
var newLaneBounds = resizeTRBL(laneShape, { top: 50 });
var expectedParticipantBounds = resizeTRBL(participantShape, { top: 50 });
// when
modeling.resizeLane(laneShape, newLaneBounds, false);
// then
expect(laneShape).to.have.bounds(newLaneBounds);
expect(participantShape).to.have.bounds(expectedParticipantBounds);
}));
it('should shrink Participant top', inject(function(elementRegistry, modeling) {
// given
var laneShape = elementRegistry.get('Lane_A'),
participantShape = elementRegistry.get('Participant_Lane');
var newParticipantBounds = resizeTRBL(participantShape, { top: 50 });
var expectedLaneBounds = resizeTRBL(laneShape, { top: 50 });
// when
modeling.resizeLane(participantShape, newParticipantBounds, false);
// then
expect(participantShape).to.have.bounds(newParticipantBounds);
expect(laneShape).to.have.bounds(expectedLaneBounds);
}));
it('should shrink Lane bottom', inject(function(elementRegistry, modeling) {
// given
var laneShape = elementRegistry.get('Lane_A'),
nextLaneShape = elementRegistry.get('Lane_B'),
nestedLaneShape = elementRegistry.get('Nested_Lane_C'),
participantShape = elementRegistry.get('Participant_Lane');
var newLaneBounds = resizeTRBL(laneShape, { bottom: -50 });
var expectedParticipantBounds = resizeTRBL(participantShape, { bottom: -50 }),
expectedNextLaneBounds = resizeTRBL(nextLaneShape, { top: -50, bottom: -50 }),
expectedNestedLaneBounds = resizeTRBL(nestedLaneShape, { bottom: -50 });
// when
modeling.resizeLane(laneShape, newLaneBounds, false);
// then
expect(laneShape).to.have.bounds(newLaneBounds);
expect(participantShape).to.have.bounds(expectedParticipantBounds);
expect(nestedLaneShape).to.have.bounds(expectedNestedLaneBounds);
expect(nextLaneShape).to.have.bounds(expectedNextLaneBounds);
}));
it('should expand Lane bottom', inject(function(elementRegistry, modeling) {
// given
var laneShape = elementRegistry.get('Lane_A'),
nextLaneShape = elementRegistry.get('Lane_B'),
nestedLaneShape = elementRegistry.get('Nested_Lane_C'),
participantShape = elementRegistry.get('Participant_Lane');
var newLaneBounds = resizeTRBL(laneShape, { bottom: 50 });
var expectedParticipantBounds = resizeTRBL(participantShape, { bottom: 50 }),
expectedNextLaneBounds = resizeTRBL(nextLaneShape, { top: 50, bottom: 50 }),
expectedNestedLaneBounds = resizeTRBL(nestedLaneShape, { bottom: 50 });
// when
modeling.resizeLane(laneShape, newLaneBounds, false);
// then
expect(laneShape).to.have.bounds(newLaneBounds);
expect(participantShape).to.have.bounds(expectedParticipantBounds);
expect(nestedLaneShape).to.have.bounds(expectedNestedLaneBounds);
expect(nextLaneShape).to.have.bounds(expectedNextLaneBounds);
}));
});
describe('enlarging / shrinking', function() {
it('should expand Lane top', inject(function(elementRegistry, modeling) {
// given
var laneShape = elementRegistry.get('Lane_A'),
participantShape = elementRegistry.get('Participant_Lane');
var newLaneBounds = resizeTRBL(laneShape, { top: -50 });
var expectedParticipantBounds = resizeTRBL(participantShape, { top: -50 });
// when
modeling.resizeLane(laneShape, newLaneBounds);
// then
expect(laneShape).to.have.bounds(newLaneBounds);
expect(participantShape).to.have.bounds(expectedParticipantBounds);
}));
it('should shrink Lane top', inject(function(elementRegistry, modeling) {
// given
var laneShape = elementRegistry.get('Lane_A'),
participantShape = elementRegistry.get('Participant_Lane');
var newLaneBounds = resizeTRBL(laneShape, { top: 50 });
var expectedParticipantBounds = resizeTRBL(participantShape, { top: 50 });
// when
modeling.resizeLane(laneShape, newLaneBounds);
// then
expect(laneShape).to.have.bounds(newLaneBounds);
expect(participantShape).to.have.bounds(expectedParticipantBounds);
}));
it('should shrink Participant top', inject(function(elementRegistry, modeling) {
// given
var laneShape = elementRegistry.get('Lane_A'),
participantShape = elementRegistry.get('Participant_Lane');
var newParticipantBounds = resizeTRBL(participantShape, { top: 50 });
var expectedLaneBounds = resizeTRBL(laneShape, { top: 50 });
// when
modeling.resizeLane(participantShape, newParticipantBounds);
// then
expect(participantShape).to.have.bounds(newParticipantBounds);
expect(laneShape).to.have.bounds(expectedLaneBounds);
}));
it('should move up above Lane', inject(function(elementRegistry, modeling) {
// given
var laneShape = elementRegistry.get('Lane_A'),
nextLaneShape = elementRegistry.get('Lane_B'),
nestedLaneShape = elementRegistry.get('Nested_Lane_C'),
participantShape = elementRegistry.get('Participant_Lane');
var newLaneBounds = resizeTRBL(laneShape, { bottom: -50 });
var expectedParticipantBounds = getBounds(participantShape),
expectedNextLaneBounds = resizeTRBL(nextLaneShape, { top: -50 + 1 /* compensation */ }),
expectedNestedLaneBounds = resizeTRBL(nestedLaneShape, { bottom: -50 });
// when
modeling.resizeLane(laneShape, newLaneBounds);
// then
expect(laneShape).to.have.bounds(newLaneBounds);
expect(participantShape).to.have.bounds(expectedParticipantBounds);
expect(nestedLaneShape).to.have.bounds(expectedNestedLaneBounds);
expect(nextLaneShape).to.have.bounds(expectedNextLaneBounds);
}));
it('should move down below Lane', inject(function(elementRegistry, modeling) {
// given
var laneShape = elementRegistry.get('Lane_A'),
nextLaneShape = elementRegistry.get('Lane_B'),
nestedLaneShape = elementRegistry.get('Nested_Lane_C'),
participantShape = elementRegistry.get('Participant_Lane');
var newLaneBounds = resizeTRBL(laneShape, { bottom: 50 });
var expectedParticipantBounds = getBounds(participantShape),
expectedNextLaneBounds = resizeTRBL(nextLaneShape, { top: 50 + 1 /* compensation */ }),
expectedNestedLaneBounds = resizeTRBL(nestedLaneShape, { bottom: 50 });
// when
modeling.resizeLane(laneShape, newLaneBounds);
// then
expect(laneShape).to.have.bounds(newLaneBounds);
expect(participantShape).to.have.bounds(expectedParticipantBounds);
expect(nestedLaneShape).to.have.bounds(expectedNestedLaneBounds);
expect(nextLaneShape).to.have.bounds(expectedNextLaneBounds);
}));
});
});
describe('horizontal', function() {
it('should expand Lane left', inject(function(elementRegistry, modeling) {
// given
var laneShape = elementRegistry.get('Lane_A'),
participantShape = elementRegistry.get('Participant_Lane');
var newLaneBounds = resizeTRBL(laneShape, { left: -50 });
var expectedParticipantBounds = resizeTRBL(participantShape, { left: -50 });
// when
modeling.resizeLane(laneShape, newLaneBounds);
// then
expect(laneShape).to.have.bounds(newLaneBounds);
expect(participantShape).to.have.bounds(expectedParticipantBounds);
}));
it('should shrink Lane left', inject(function(elementRegistry, modeling) {
// given
var laneShape = elementRegistry.get('Lane_A'),
participantShape = elementRegistry.get('Participant_Lane');
var newLaneBounds = resizeTRBL(laneShape, { left: 50 });
var expectedParticipantBounds = resizeTRBL(participantShape, { left: 50 });
// when
modeling.resizeLane(laneShape, newLaneBounds);
// then
expect(laneShape).to.have.bounds(newLaneBounds);
expect(participantShape).to.have.bounds(expectedParticipantBounds);
}));
it('should shrink Participant left', inject(function(elementRegistry, modeling) {
// given
var laneShape = elementRegistry.get('Lane_A'),
participantShape = elementRegistry.get('Participant_Lane');
var newParticipantBounds = resizeTRBL(participantShape, { left: 50 });
var expectedLaneBounds = resizeTRBL(laneShape, { left: 50 });
// when
modeling.resizeLane(participantShape, newParticipantBounds);
// then
expect(participantShape).to.have.bounds(newParticipantBounds);
expect(laneShape).to.have.bounds(expectedLaneBounds);
}));
it('should shrink Lane right', inject(function(elementRegistry, modeling) {
// given
var laneShape = elementRegistry.get('Lane_A'),
nextLaneShape = elementRegistry.get('Lane_B'),
nestedLaneShape = elementRegistry.get('Nested_Lane_C'),
participantShape = elementRegistry.get('Participant_Lane');
var newLaneBounds = resizeTRBL(laneShape, { right: -50 });
var expectedParticipantBounds = resizeTRBL(participantShape, { right: -50 }),
expectedNextLaneBounds = resizeTRBL(nextLaneShape, { right: -50 }),
expectedNestedLaneBounds = resizeTRBL(nestedLaneShape, { right: -50 });
// when
modeling.resizeLane(laneShape, newLaneBounds);
// then
expect(laneShape).to.have.bounds(newLaneBounds);
expect(participantShape).to.have.bounds(expectedParticipantBounds);
expect(nestedLaneShape).to.have.bounds(expectedNestedLaneBounds);
expect(nextLaneShape).to.have.bounds(expectedNextLaneBounds);
}));
it('should expand Lane right', inject(function(elementRegistry, modeling) {
// given
var laneShape = elementRegistry.get('Lane_A'),
nextLaneShape = elementRegistry.get('Lane_B'),
nestedLaneShape = elementRegistry.get('Nested_Lane_C'),
participantShape = elementRegistry.get('Participant_Lane');
var newLaneBounds = resizeTRBL(laneShape, { right: 50 });
var expectedParticipantBounds = resizeTRBL(participantShape, { right: 50 }),
expectedNextLaneBounds = resizeTRBL(nextLaneShape, { right: 50 }),
expectedNestedLaneBounds = resizeTRBL(nestedLaneShape, { right: 50 });
// when
modeling.resizeLane(laneShape, newLaneBounds);
// then
expect(laneShape).to.have.bounds(newLaneBounds);
expect(participantShape).to.have.bounds(expectedParticipantBounds);
expect(nestedLaneShape).to.have.bounds(expectedNestedLaneBounds);
expect(nextLaneShape).to.have.bounds(expectedNextLaneBounds);
}));
});
});
================================================
FILE: test/spec/features/modeling/lanes/SplitLane.nested.bpmn
================================================
Event
Task
SequenceFlow_192h0e0
SequenceFlow_192h0e0
================================================
FILE: test/spec/features/modeling/lanes/SplitLane.nested.vertical.bpmn
================================================
Event_2
Activity_2
Flow_0vlrl5f
Flow_0vlrl5f
================================================
FILE: test/spec/features/modeling/lanes/SplitLaneSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
var pick = require('min-dash').pick;
import modelingModule from 'lib/features/modeling';
import coreModule from 'lib/core';
var getChildLanes = require('lib/features/modeling/util/LaneUtil').getChildLanes;
function getBounds(element) {
return pick(element, [ 'x', 'y', 'width', 'height' ]);
}
describe('features/modeling - SplitLane', function() {
var testModules = [ coreModule, modelingModule ];
describe('should split Participant with Lane', function() {
var diagramXML = require('./participant-lane.bpmn');
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
it('into two lanes', inject(function(elementRegistry, modeling) {
// given
var participantShape = elementRegistry.get('Participant_Lane'),
existingLane = elementRegistry.get('Lane'),
oldBounds = getBounds(participantShape);
// when
modeling.splitLane(participantShape, 2);
var childLanes = getChildLanes(participantShape);
var newLaneHeight = Math.round(participantShape.height / 2);
// then
// participant has original size
expect(participantShape).to.have.bounds(oldBounds);
// and two child lanes
expect(childLanes.length).to.eql(2);
// with the first lane being the original one
expect(childLanes[0]).to.equal(existingLane);
// with respective bounds
expect(childLanes[0]).to.have.bounds({
x: participantShape.x + 30,
y: participantShape.y,
width: participantShape.width - 30,
height: newLaneHeight
});
expect(childLanes[1]).to.have.bounds({
x: participantShape.x + 30,
y: participantShape.y + newLaneHeight,
width: participantShape.width - 30,
height: newLaneHeight - 1 // compensate for rounding issues
});
// with participant's direction
expect(childLanes[0].di.isHorizontal).to.be.true;
expect(childLanes[1].di.isHorizontal).to.be.true;
}));
it('into three lanes', inject(function(elementRegistry, modeling) {
// given
var participantShape = elementRegistry.get('Participant_Lane'),
existingLane = elementRegistry.get('Lane'),
oldBounds = getBounds(participantShape);
// when
modeling.splitLane(participantShape, 3);
var childLanes = getChildLanes(participantShape);
var newLaneHeight = Math.round(participantShape.height / 3);
// then
// participant has original size
expect(participantShape).to.have.bounds(oldBounds);
// and two child lanes
expect(childLanes.length).to.eql(3);
// with the first lane being the original one
expect(childLanes[0]).to.equal(existingLane);
// with respective bounds
expect(childLanes[0]).to.have.bounds({
x: participantShape.x + 30,
y: participantShape.y,
width: participantShape.width - 30,
height: newLaneHeight
});
expect(childLanes[1]).to.have.bounds({
x: participantShape.x + 30,
y: participantShape.y + newLaneHeight,
width: participantShape.width - 30,
height: newLaneHeight
});
expect(childLanes[2]).to.have.bounds({
x: participantShape.x + 30,
y: participantShape.y + newLaneHeight * 2,
width: participantShape.width - 30,
height: newLaneHeight - 1 // compensate for rounding issues
});
// with participant's direction
expect(childLanes[0].di.isHorizontal).to.be.true;
expect(childLanes[1].di.isHorizontal).to.be.true;
expect(childLanes[2].di.isHorizontal).to.be.true;
}));
});
describe('should split vertical Participant with Lane', function() {
var diagramXML = require('./participant-lane-vertical.bpmn');
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
it('into two lanes', inject(function(elementRegistry, modeling) {
// given
var participantShape = elementRegistry.get('Vertical_Participant_Lane'),
existingLane = elementRegistry.get('Vertical_Lane'),
oldBounds = getBounds(participantShape);
// when
modeling.splitLane(participantShape, 2);
var childLanes = getChildLanes(participantShape);
var newLaneWidth = Math.round(participantShape.width / 2);
// then
// participant has original size
expect(participantShape).to.have.bounds(oldBounds);
// and two child lanes
expect(childLanes.length).to.eql(2);
// with the first lane being the original one
expect(childLanes[0]).to.equal(existingLane);
// with respective bounds
expect(childLanes[0]).to.have.bounds({
x: participantShape.x,
y: participantShape.y + 30,
width: newLaneWidth,
height: participantShape.height - 30
});
expect(childLanes[1]).to.have.bounds({
x: participantShape.x + newLaneWidth,
y: participantShape.y + 30,
width: newLaneWidth - 1, // compensate for rounding issues
height: participantShape.height - 30
});
// with participant's direction
expect(childLanes[0].di.isHorizontal).to.be.false;
expect(childLanes[1].di.isHorizontal).to.be.false;
}));
it('into three lanes', inject(function(elementRegistry, modeling) {
// given
var participantShape = elementRegistry.get('Vertical_Participant_Lane'),
existingLane = elementRegistry.get('Vertical_Lane'),
oldBounds = getBounds(participantShape);
// when
modeling.splitLane(participantShape, 3);
var childLanes = getChildLanes(participantShape);
var newLaneWidth = Math.round(participantShape.width / 3);
// then
// participant has original size
expect(participantShape).to.have.bounds(oldBounds);
// and three child lanes
expect(childLanes.length).to.eql(3);
// with the first lane being the original one
expect(childLanes[0]).to.equal(existingLane);
// with respective bounds
expect(childLanes[0]).to.have.bounds({
x: participantShape.x,
y: participantShape.y + 30,
width: newLaneWidth,
height: participantShape.height - 30
});
expect(childLanes[1]).to.have.bounds({
x: participantShape.x + newLaneWidth,
y: participantShape.y + 30,
width: newLaneWidth,
height: participantShape.height - 30
});
expect(childLanes[2]).to.have.bounds({
x: participantShape.x + newLaneWidth * 2,
y: participantShape.y + 30,
width: newLaneWidth - 1, // compensate for rounding issues
height: participantShape.height - 30
});
// with participant's direction
expect(childLanes[0].di.isHorizontal).to.be.false;
expect(childLanes[1].di.isHorizontal).to.be.false;
expect(childLanes[2].di.isHorizontal).to.be.false;
}));
});
describe('should split Participant without Lane', function() {
var diagramXML = require('./participant-no-lane.bpmn');
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
it('into two lanes', inject(function(elementRegistry, modeling) {
// given
var participantShape = elementRegistry.get('Participant_No_Lane'),
oldBounds = getBounds(participantShape);
// when
modeling.splitLane(participantShape, 2);
var childLanes = getChildLanes(participantShape);
var newLaneHeight = Math.round(participantShape.height / 2);
// then
// participant has original size
expect(participantShape).to.have.bounds(oldBounds);
// and two child lanes
expect(childLanes).to.have.length(2);
// with respective bounds
expect(childLanes[0]).to.have.bounds({
x: participantShape.x + 30,
y: participantShape.y,
width: participantShape.width - 30,
height: newLaneHeight
});
expect(childLanes[1]).to.have.bounds({
x: participantShape.x + 30,
y: participantShape.y + newLaneHeight,
width: participantShape.width - 30,
height: newLaneHeight
});
// with participant's direction
expect(childLanes[0].di.isHorizontal).to.be.true;
expect(childLanes[1].di.isHorizontal).to.be.true;
}));
it('into three lanes', inject(function(elementRegistry, modeling) {
// given
var participantShape = elementRegistry.get('Participant_No_Lane'),
oldBounds = getBounds(participantShape);
// when
modeling.splitLane(participantShape, 3);
var childLanes = getChildLanes(participantShape);
var newLaneHeight = Math.round(participantShape.height / 3);
// then
// participant has original size
expect(participantShape).to.have.bounds(oldBounds);
// and two child lanes
expect(childLanes).to.have.length(3);
// with respective bounds
expect(childLanes[0]).to.have.bounds({
x: participantShape.x + 30,
y: participantShape.y,
width: participantShape.width - 30,
height: newLaneHeight
});
expect(childLanes[1]).to.have.bounds({
x: participantShape.x + 30,
y: participantShape.y + newLaneHeight,
width: participantShape.width - 30,
height: newLaneHeight
});
expect(childLanes[2]).to.have.bounds({
x: participantShape.x + 30,
y: participantShape.y + newLaneHeight * 2,
width: participantShape.width - 30,
height: newLaneHeight + 1 // compensate for rounding issues
});
// with participant's direction
expect(childLanes[0].di.isHorizontal).to.be.true;
expect(childLanes[1].di.isHorizontal).to.be.true;
expect(childLanes[2].di.isHorizontal).to.be.true;
}));
});
describe('should split vertical Participant without Lane', function() {
var diagramXML = require('./participant-no-lane-vertical.bpmn');
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
it('into two lanes', inject(function(elementRegistry, modeling) {
// given
var participantShape = elementRegistry.get('Vertical_Participant_No_Lane'),
oldBounds = getBounds(participantShape);
// when
modeling.splitLane(participantShape, 2);
var childLanes = getChildLanes(participantShape);
var newLaneWidth = Math.round(participantShape.width / 2);
// then
// participant has original size
expect(participantShape).to.have.bounds(oldBounds);
// and two child lanes
expect(childLanes).to.have.length(2);
// with respective bounds
expect(childLanes[0]).to.have.bounds({
x: participantShape.x,
y: participantShape.y + 30,
width: newLaneWidth,
height: participantShape.height - 30
});
expect(childLanes[1]).to.have.bounds({
x: participantShape.x + newLaneWidth,
y: participantShape.y + 30,
width: newLaneWidth,
height: participantShape.height - 30
});
// with participant's direction
expect(childLanes[0].di.isHorizontal).to.be.false;
expect(childLanes[1].di.isHorizontal).to.be.false;
}));
it('into three lanes', inject(function(elementRegistry, modeling) {
// given
var participantShape = elementRegistry.get('Vertical_Participant_No_Lane'),
oldBounds = getBounds(participantShape);
// when
modeling.splitLane(participantShape, 3);
var childLanes = getChildLanes(participantShape);
var newLaneWidth = Math.round(participantShape.width / 3);
// then
// participant has original size
expect(participantShape).to.have.bounds(oldBounds);
// and three child lanes
expect(childLanes).to.have.length(3);
// with respective bounds
expect(childLanes[0]).to.have.bounds({
x: participantShape.x,
y: participantShape.y + 30,
width: newLaneWidth,
height: participantShape.height - 30
});
expect(childLanes[1]).to.have.bounds({
x: participantShape.x + newLaneWidth,
y: participantShape.y + 30,
width: newLaneWidth,
height: participantShape.height - 30
});
expect(childLanes[2]).to.have.bounds({
x: participantShape.x + newLaneWidth * 2,
y: participantShape.y + 30,
width: newLaneWidth + 1, // compensate for rounding issues
height: participantShape.height - 30
});
// with participant's direction
expect(childLanes[0].di.isHorizontal).to.be.false;
expect(childLanes[1].di.isHorizontal).to.be.false;
expect(childLanes[2].di.isHorizontal).to.be.false;
}));
});
describe('should split nested Lane', function() {
var diagramXML = require('./SplitLane.nested.bpmn');
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
it('into two lanes', inject(function(elementRegistry, modeling) {
// given
var laneShape = elementRegistry.get('Lane'),
laneBo = laneShape.businessObject,
oldBounds = getBounds(laneShape);
// when
modeling.splitLane(laneShape, 2);
var childLanes = getChildLanes(laneShape);
var newLaneHeight = Math.round(laneShape.height / 2);
// then
// participant has original size
expect(laneShape).to.have.bounds(oldBounds);
// and two child lanes
expect(childLanes).to.have.length(2);
// with respective bounds
expect(childLanes[0]).to.have.bounds({
x: laneShape.x + 30,
y: laneShape.y,
width: laneShape.width - 30,
height: newLaneHeight
});
expect(childLanes[1]).to.have.bounds({
x: laneShape.x + 30,
y: laneShape.y + newLaneHeight,
width: laneShape.width - 30,
height: newLaneHeight
});
// BPMN internals are properly updated
expect(laneBo.childLaneSet).to.exist;
expect(laneBo.childLaneSet.lanes).to.eql([
childLanes[0].businessObject,
childLanes[1].businessObject
]);
// with parent lane's direction
expect(childLanes[0].di.isHorizontal).to.be.true;
expect(childLanes[1].di.isHorizontal).to.be.true;
}));
});
describe('should split nested vertical Lane', function() {
var diagramXML = require('./SplitLane.nested.vertical.bpmn');
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
it('into two lanes', inject(function(elementRegistry, modeling) {
// given
var laneShape = elementRegistry.get('Vertical_Lane'),
laneBo = laneShape.businessObject,
oldBounds = getBounds(laneShape);
// when
modeling.splitLane(laneShape, 2);
var childLanes = getChildLanes(laneShape);
var newLaneWidth = Math.round(laneShape.width / 2);
// then
// participant has original size
expect(laneShape).to.have.bounds(oldBounds);
// and two child lanes
expect(childLanes).to.have.length(2);
// with respective bounds
expect(childLanes[0]).to.have.bounds({
x: laneShape.x,
y: laneShape.y + 30,
width: newLaneWidth,
height: laneShape.height - 30
});
expect(childLanes[1]).to.have.bounds({
x: laneShape.x + newLaneWidth,
y: laneShape.y + 30,
width: newLaneWidth,
height: laneShape.height - 30
});
// BPMN internals are properly updated
expect(laneBo.childLaneSet).to.exist;
expect(laneBo.childLaneSet.lanes).to.eql([
childLanes[0].businessObject,
childLanes[1].businessObject
]);
// with parent lane's direction
expect(childLanes[0].di.isHorizontal).to.be.false;
expect(childLanes[1].di.isHorizontal).to.be.false;
}));
});
});
================================================
FILE: test/spec/features/modeling/lanes/UpdateFlowNodeRefs.basic.bpmn
================================================
Task_A
Event
SequenceFlow
SequenceFlow
Task_C
IntermediateThrowEvent
Task_D
Task_E
Task_E
================================================
FILE: test/spec/features/modeling/lanes/UpdateFlowNodeRefsSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import modelingModule from 'lib/features/modeling';
import coreModule from 'lib/core';
import copyPasteModule from 'lib/features/copy-paste';
import {
find
} from 'min-dash';
describe('features/modeling - lanes - flowNodeRefs', function() {
var diagramXML = require('./UpdateFlowNodeRefs.basic.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: [
coreModule,
modelingModule,
copyPasteModule
]
}));
describe('should unwire during move', function() {
it('execute', inject(function(elementRegistry, modeling) {
// given
var taskShape = elementRegistry.get('Task_A'),
task = taskShape.businessObject,
sourceLaneShape = elementRegistry.get('Lane'),
sourceLane = sourceLaneShape.businessObject,
targetParticipantShape = elementRegistry.get('Participant_B');
// when
modeling.moveElements([ taskShape ], { x: 0, y: +200 }, targetParticipantShape);
// then
expect(sourceLane.flowNodeRef).not.to.contain(task);
}));
it('undo', inject(function(elementRegistry, commandStack, modeling) {
// given
var taskShape = elementRegistry.get('Task_A'),
task = taskShape.businessObject,
sourceLaneShape = elementRegistry.get('Lane'),
sourceLane = sourceLaneShape.businessObject,
targetParticipantShape = elementRegistry.get('Participant_B');
modeling.moveElements([ taskShape ], { x: 0, y: +200 }, targetParticipantShape);
// when
commandStack.undo();
// then
expect(sourceLane.flowNodeRef).to.contain(task);
}));
it('redo', inject(function(elementRegistry, commandStack, modeling) {
// given
var taskShape = elementRegistry.get('Task_A'),
task = taskShape.businessObject,
sourceLaneShape = elementRegistry.get('Lane'),
sourceLane = sourceLaneShape.businessObject,
targetParticipantShape = elementRegistry.get('Participant_B');
modeling.moveElements([ taskShape ], { x: 0, y: +200 }, targetParticipantShape);
// when
commandStack.undo();
commandStack.redo();
// then
expect(sourceLane.flowNodeRef).not.to.contain(task);
}));
});
describe('should wire during move', function() {
it('execute', inject(function(elementRegistry, modeling) {
// given
var taskShape = elementRegistry.get('Task_B'),
task = taskShape.businessObject,
targetLaneShape = elementRegistry.get('Lane'),
targetLane = targetLaneShape.businessObject;
// when
modeling.moveElements([ taskShape ], { x: 0, y: -200 }, targetLaneShape);
// then
expect(targetLane.flowNodeRef).to.contain(task);
}));
it('undo', inject(function(elementRegistry, commandStack, modeling) {
// given
var taskShape = elementRegistry.get('Task_B'),
task = taskShape.businessObject,
targetLaneShape = elementRegistry.get('Lane'),
targetLane = targetLaneShape.businessObject;
modeling.moveElements([ taskShape ], { x: 0, y: -200 }, targetLaneShape);
// when
commandStack.undo();
// then
expect(targetLane.flowNodeRef).not.to.contain(task);
}));
it('redo', inject(function(elementRegistry, commandStack, modeling) {
// given
var taskShape = elementRegistry.get('Task_B'),
task = taskShape.businessObject,
targetLaneShape = elementRegistry.get('Lane'),
targetLane = targetLaneShape.businessObject;
modeling.moveElements([ taskShape ], { x: 0, y: -200 }, targetLaneShape);
// when
commandStack.undo();
commandStack.redo();
// then
expect(targetLane.flowNodeRef).to.contain(task);
}));
});
describe('should unwire during delete', function() {
it('execute', inject(function(elementRegistry, modeling) {
// given
var taskShape = elementRegistry.get('Task_A'),
task = taskShape.businessObject,
parentLaneShape = elementRegistry.get('Lane'),
parentLane = parentLaneShape.businessObject;
// when
modeling.removeElements([ taskShape ]);
// then
expect(parentLane.flowNodeRef).not.to.contain(task);
}));
it('undo', inject(function(elementRegistry, commandStack, modeling) {
// given
var taskShape = elementRegistry.get('Task_A'),
task = taskShape.businessObject,
parentLaneShape = elementRegistry.get('Lane'),
parentLane = parentLaneShape.businessObject;
modeling.removeElements([ taskShape ]);
// when
commandStack.undo();
// then
expect(parentLane.flowNodeRef).to.contain(task);
}));
it('redo', inject(function(elementRegistry, commandStack, modeling) {
// given
var taskShape = elementRegistry.get('Task_A'),
task = taskShape.businessObject,
parentLaneShape = elementRegistry.get('Lane'),
parentLane = parentLaneShape.businessObject;
modeling.removeElements([ taskShape ]);
// when
commandStack.undo();
commandStack.redo();
// then
expect(parentLane.flowNodeRef).not.to.contain(task);
}));
});
describe('should wire during create', function() {
it('execute', inject(function(elementRegistry, modeling) {
// given
var taskShape, task,
parentLaneShape = elementRegistry.get('Lane'),
parentLane = parentLaneShape.businessObject;
// when
taskShape = modeling.createShape({ type: 'bpmn:Task' }, { x: 500, y: 150 }, parentLaneShape);
task = taskShape.businessObject;
// then
expect(parentLane.flowNodeRef).to.contain(task);
}));
it('undo', inject(function(elementRegistry, commandStack, modeling) {
// given
var taskShape, task,
parentLaneShape = elementRegistry.get('Lane'),
parentLane = parentLaneShape.businessObject;
taskShape = modeling.createShape({ type: 'bpmn:Task' }, { x: 500, y: 150 }, parentLaneShape);
task = taskShape.businessObject;
// when
commandStack.undo();
// then
expect(parentLane.flowNodeRef).not.to.contain(task);
}));
it('redo', inject(function(elementRegistry, commandStack, modeling) {
// given
var taskShape, task,
parentLaneShape = elementRegistry.get('Lane'),
parentLane = parentLaneShape.businessObject;
taskShape = modeling.createShape({ type: 'bpmn:Task' }, { x: 500, y: 150 }, parentLaneShape);
task = taskShape.businessObject;
// when
commandStack.undo();
commandStack.redo();
// then
expect(parentLane.flowNodeRef).to.contain(task);
}));
});
it('should unwire moving multiple', inject(function(elementRegistry, modeling) {
// given
var taskShape = elementRegistry.get('Task_A'),
task = taskShape.businessObject,
eventShape = elementRegistry.get('Event'),
event = eventShape.businessObject,
targetParticipantShape = elementRegistry.get('Participant_B'),
sourceLaneShape = elementRegistry.get('Lane'),
sourceLane = sourceLaneShape.businessObject;
// when
modeling.moveElements([ taskShape, eventShape ], { x: 0, y: +200 }, targetParticipantShape);
// then
expect(sourceLane.flowNodeRef).not.to.contain(task);
expect(sourceLane.flowNodeRef).not.to.contain(event);
}));
it('should not create duplicate refs on attaching / detaching', inject(function(elementRegistry, modeling) {
// given
var eventID = 'IntermediateThrowEvent',
throwEvent = elementRegistry.get(eventID),
task1 = elementRegistry.get('Task_C'),
task2 = elementRegistry.get('Task_D'),
lane1 = elementRegistry.get('Participant_C_Lane_1').businessObject,
lane2 = elementRegistry.get('Participant_C_Lane_2').businessObject;
// when
modeling.moveElements([ throwEvent ], { x: -280, y: 30 }, task1, { attach: true });
var boundaryEvent = elementRegistry.get(eventID);
modeling.moveElements([ boundaryEvent ], { x: 0, y: 150 }, task2, { attach: true });
// then
expect(lane1.flowNodeRef).not.to.contain(boundaryEvent.businessObject);
expect(lane2.flowNodeRef).to.contain(boundaryEvent.businessObject);
expect(lane1.flowNodeRef).to.have.length(1);
expect(lane2.flowNodeRef).to.have.length(2);
}));
describe('should wire once during paste', function() {
it('execute', inject(function(canvas, eventBus, elementRegistry, copyPaste) {
// given
var participant = elementRegistry.get('Participant_D');
var updateRefsSpy = sinon.spy();
eventBus.on('commandStack.lane.updateRefs.execute', updateRefsSpy);
// when
copyPaste.copy(participant);
var pastedElements = copyPaste.paste({
element: canvas.getRootElement(),
point: {
x: 350,
y: 150
}
});
var pastedLane = find(pastedElements, function(e) {
return e.businessObject.name === 'Lane_D_1_1';
});
var pastedTask = find(pastedElements, function(e) {
return e.businessObject.name === 'Task_E';
});
// then
expect(updateRefsSpy).to.have.been.calledOnce;
expect(pastedLane.businessObject.flowNodeRef).to.include(pastedTask.businessObject);
}));
});
});
================================================
FILE: test/spec/features/modeling/lanes/lanes-flow-nodes-vertical.bpmn
================================================
V_Task_Boundary
V_Task
V_Event
V_Boundary
V_Task_Boundary
V_Task
V_Event
V_Boundary
Flow_V
Flow_From_V_Boundary
Flow_V
Flow_From_V_Boundary
================================================
FILE: test/spec/features/modeling/lanes/lanes-flow-nodes.bpmn
================================================
Task_Boundary
Task
Event
Boundary
Task_Boundary
Task
Event
Boundary
SequenceFlow
SequenceFlow_From_Boundary
SequenceFlow
SequenceFlow_From_Boundary
================================================
FILE: test/spec/features/modeling/lanes/lanes.bpmn
================================================
Boundary
Task_Boundary
Task
SequenceFlow_From_Boundary
SequenceFlow
SequenceFlow_From_Boundary
SequenceFlow
================================================
FILE: test/spec/features/modeling/lanes/lanes.only.bpmn
================================================
================================================
FILE: test/spec/features/modeling/lanes/lanes.only.vertical.bpmn
================================================
================================================
FILE: test/spec/features/modeling/lanes/lanes.vertical.bpmn
================================================
V_Task
V_Task_Boundary
V_Boundary
V_Task
V_Task_Boundary
V_Boundary
Flow_V
Flow_From_V_Boundary
Flow_V
Flow_From_V_Boundary
================================================
FILE: test/spec/features/modeling/lanes/participant-lane-vertical.bpmn
================================================
Task_2
================================================
FILE: test/spec/features/modeling/lanes/participant-lane.bpmn
================================================
Task_1
================================================
FILE: test/spec/features/modeling/lanes/participant-no-lane-vertical.bpmn
================================================
================================================
FILE: test/spec/features/modeling/lanes/participant-no-lane.bpmn
================================================
================================================
FILE: test/spec/features/modeling/lanes/participant-single-lane.bpmn
================================================
================================================
FILE: test/spec/features/modeling/layout/Helper.js
================================================
import {
getBpmnJS
} from 'test/TestHelper';
export function connect(source, target, attrs) {
var elementRegistry = getBpmnJS().get('elementRegistry'),
modeling = getBpmnJS().get('modeling');
var sourceElement = typeof source === 'string' ? elementRegistry.get(source) : source;
var targetElement = typeof target === 'string' ? elementRegistry.get(target) : target;
// assume
expect(sourceElement).to.exist;
expect(targetElement).to.exist;
return modeling.connect(sourceElement, targetElement, attrs);
}
export function reconnectEnd(connection, target, docking) {
var elementRegistry = getBpmnJS().get('elementRegistry'),
modeling = getBpmnJS().get('modeling');
var connectionElement = typeof connection === 'string' ? elementRegistry.get(connection) : connection;
var targetElement = typeof target === 'string' ? elementRegistry.get(target) : target;
// assume
expect(connectionElement).to.exist;
expect(targetElement).to.exist;
return modeling.reconnectEnd(connectionElement, targetElement, docking);
}
export function element(id) {
return getBpmnJS().get('elementRegistry').get(id);
}
export function move(shape, delta) {
var elementRegistry = getBpmnJS().get('elementRegistry'),
modeling = getBpmnJS().get('modeling');
var shapeElement = typeof shape === 'string' ? elementRegistry.get(shape) : shape;
// assume
expect(shapeElement).to.exist;
modeling.moveElements([ shapeElement ], delta);
return shapeElement;
}
// debugging
export function inspect(element) {
console.log(JSON.stringify(element));
}
================================================
FILE: test/spec/features/modeling/layout/LayoutAssociationSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import modelingModule from 'lib/features/modeling';
import coreModule from 'lib/core';
describe('features/modeling - layout association', function() {
var diagramXML = require('../../../../fixtures/bpmn/basic.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: [
coreModule,
modelingModule
]
}));
var rootShape;
beforeEach(inject(function(canvas) {
rootShape = canvas.getRootElement();
}));
it('should layout straight after TextAnnotation creation', inject(function(elementRegistry, modeling) {
// given
var startEventShape = elementRegistry.get('StartEvent_1');
// when
var textAnnotationShape = modeling.createShape({ type: 'bpmn:TextAnnotation' }, { x: 400, y: 400 }, rootShape);
modeling.connect(textAnnotationShape, startEventShape);
var waypoints = textAnnotationShape.outgoing[0].waypoints;
// then
expect(waypoints).to.eql([
{ original: { x: 400, y: 400 }, x: 389, y: 385 },
{ original: { x: 191, y: 120 }, x: 202, y: 134 }
]);
}));
it('should layout straight after TextAnnotation move', inject(function(elementRegistry, modeling) {
// given
var startEventShape = elementRegistry.get('StartEvent_1'),
textAnnotationShape = modeling.createShape({ type: 'bpmn:TextAnnotation' }, { x: 400, y: 400 }, rootShape);
modeling.connect(textAnnotationShape, startEventShape);
// when
modeling.moveElements([ textAnnotationShape ], { x: 20, y: 0 }, rootShape);
var waypoints = textAnnotationShape.outgoing[0].waypoints;
// then
expect(waypoints).to.eql([
{ original: { x: 420, y: 400 }, x: 408, y: 385 },
{ original: { x: 191, y: 120 }, x: 202, y: 134 }
]);
}));
it('should retain waypoints after TextAnnotation move', inject(function(elementRegistry, modeling) {
// given
var startEventShape = elementRegistry.get('StartEvent_1'),
textAnnotationShape = modeling.createShape({ type: 'bpmn:TextAnnotation' }, { x: 400, y: 400 }, rootShape);
var connection = modeling.connect(textAnnotationShape, startEventShape),
waypoints = connection.waypoints;
// add a waypoint
waypoints.splice(1, 0, { x: 400, y: 300 });
modeling.updateWaypoints(connection, waypoints);
// when
modeling.moveElements([ textAnnotationShape ], { x: 20, y: 0 }, rootShape);
// then
expect(connection).to.have.waypoints([
{ original: { x: 420, y: 400 }, x: 417, y: 385 },
{ x: 400, y: 300 },
{ original: { x: 191, y: 120 }, x: 204, y: 132 }
]);
}));
});
================================================
FILE: test/spec/features/modeling/layout/LayoutConnectionSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import { getDi } from 'lib/util/ModelUtil';
import modelingModule from 'lib/features/modeling';
import coreModule from 'lib/core';
import bendpointsModule from 'diagram-js/lib/features/bendpoints';
import connectionPreviewModule from 'diagram-js/lib/features/connection-preview';
import connectModule from 'diagram-js/lib/features/connect';
import createModule from 'diagram-js/lib/features/create';
import {
createCanvasEvent as canvasEvent
} from '../../../../util/MockEvents';
var testModules = [
bendpointsModule,
connectionPreviewModule,
connectModule,
coreModule,
createModule,
modelingModule
];
describe('features/modeling - layout connection', function() {
var diagramXML = require('../../../../fixtures/bpmn/sequence-flows.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
describe('should not change already layouted', function() {
it('execute', inject(function(elementRegistry, modeling, bpmnFactory) {
// given
var sequenceFlowConnection = elementRegistry.get('SequenceFlow_1'),
sequenceFlowDi = getDi(sequenceFlowConnection);
var expectedWaypoints = sequenceFlowConnection.waypoints;
// when
modeling.layoutConnection(sequenceFlowConnection);
// then
// expect cropped, repaired connection
// that was not actually modified
expect(sequenceFlowConnection.waypoints).to.eql(expectedWaypoints);
// expect cropped waypoints in di
var diWaypoints = bpmnFactory.createDiWaypoints(expectedWaypoints);
expect(sequenceFlowDi.waypoint).eql(diWaypoints);
}));
it('undo', inject(function(elementRegistry, commandStack, modeling) {
// given
var sequenceFlowConnection = elementRegistry.get('SequenceFlow_1'),
sequenceFlowDi = getDi(sequenceFlowConnection);
var oldWaypoints = sequenceFlowConnection.waypoints,
oldDiWaypoints = sequenceFlowDi.waypoint;
modeling.layoutConnection(sequenceFlowConnection);
// when
commandStack.undo();
// then
expect(sequenceFlowConnection.waypoints).eql(oldWaypoints);
expect(sequenceFlowDi.waypoint).eql(oldDiWaypoints);
}));
it('redo', inject(function(elementRegistry, commandStack, modeling) {
// given
var sequenceFlowConnection = elementRegistry.get('SequenceFlow_1'),
sequenceFlowDi = getDi(sequenceFlowConnection);
modeling.layoutConnection(sequenceFlowConnection);
var newWaypoints = sequenceFlowConnection.waypoints,
newDiWaypoints = sequenceFlowDi.waypoint;
// when
commandStack.undo();
commandStack.redo();
// then
expect(sequenceFlowConnection.waypoints).eql(newWaypoints);
expect(sequenceFlowDi.waypoint).eql(newDiWaypoints);
}));
});
it('should remove un-needed waypoints', inject(function(elementRegistry, modeling) {
// given
var taskShape = elementRegistry.get('Task_2'),
sequenceFlowConnection = elementRegistry.get('SequenceFlow_1');
// when
// moving task
modeling.moveElements([ taskShape ], { x: 250, y: -95 });
// then
var newWaypoints = sequenceFlowConnection.waypoints;
expect(newWaypoints.map(toPoint)).to.eql([
{ x: 578, y: 341 },
{ x: 982, y: 341 }
]);
}));
describe('integration', function() {
describe('re-connection', function() {
it('should correctly layout after start re-connection', inject(function(elementRegistry, modeling) {
// given
var task1 = elementRegistry.get('Task_1'),
connection = elementRegistry.get('SequenceFlow_1'),
docking = { x: 292, y: 376 };
// when
modeling.reconnectStart(connection, task1, docking);
// then
var waypoints = connection.waypoints,
i,
first,
second;
for (i = 0; i < waypoints.length - 1; i++) {
first = waypoints[i];
second = waypoints[i + 1];
expect(areOnSameAxis(first, second), 'points are on different axes').to.be.true;
}
}));
it('should correctly layout after end re-connection', inject(function(elementRegistry, modeling) {
// given
var task1 = elementRegistry.get('Task_1'),
connection = elementRegistry.get('SequenceFlow_1'),
docking = { x: 292, y: 376 };
// when
modeling.reconnectEnd(connection, task1, docking);
// then
var waypoints = connection.waypoints,
i,
first,
second;
for (i = 0; i < waypoints.length - 1; i++) {
first = waypoints[i];
second = waypoints[i + 1];
expect(areOnSameAxis(first, second), 'points are on different axes').to.be.true;
}
}));
});
describe('connection preview', function() {
var task;
beforeEach(inject(function(elementFactory, dragging) {
task = elementFactory.createShape({
type: 'bpmn:Task'
});
dragging.setOptions({ manual: true });
}));
afterEach(inject(function(dragging) {
dragging.setOptions({ manual: false });
}));
it.skip('should correctly lay out connection preview on create',
inject(function(canvas, create, dragging, elementRegistry) {
// given
var rootShape = canvas.getRootElement(),
rootShapeGfx = canvas.getGraphics(rootShape),
task1 = elementRegistry.get('Task_1');
// when
create.start(canvasEvent({ x: 0, y: 0 }), task, task1);
dragging.move(canvasEvent({ x: 175, y: 175 }));
dragging.hover({ element: rootShape, gfx: rootShapeGfx });
dragging.move(canvasEvent({ x: 200, y: 200 }));
var ctx = dragging.context();
var context = ctx.data.context;
var connectionPreview = context.getConnection(
context.canExecute.connect,
context.source,
context.shape
);
var waypointsPreview = connectionPreview.waypoints.slice();
dragging.end();
// then
expect(task1.outgoing[0]).to.exist;
expect(task1.outgoing[0].waypoints).to.deep.eql(waypointsPreview);
})
);
it('should correctly lay out new connection preview',
inject(function(connect, dragging, elementRegistry) {
// given
var task1 = elementRegistry.get('Task_1'),
task2 = elementRegistry.get('Task_2');
// when
connect.start(canvasEvent({ x: 0, y: 0 }), task1);
dragging.move(canvasEvent({ x: 760, y: 420 }));
dragging.hover({ element: task2 });
dragging.move(canvasEvent({ x: 782, y: 436 }));
var ctx = dragging.context();
var context = ctx.data.context;
var connectionPreview = context.getConnection(
context.canExecute,
context.source,
context.target
);
var waypointsPreview = connectionPreview.waypoints.slice();
dragging.end();
// then
expect(task1.outgoing[0]).to.exist;
expect(task1.outgoing[0].waypoints).to.deep.eql(waypointsPreview);
})
);
it('should correctly lay out connection preview on reconnect start',
inject(function(canvas, bendpointMove, dragging, elementRegistry) {
// given
var task1 = elementRegistry.get('Task_1'),
task1Gfx = canvas.getGraphics(task1),
sequenceFlow2 = elementRegistry.get('SequenceFlow_2');
// when
bendpointMove.start(canvasEvent({ x: 0, y: 0 }), sequenceFlow2, 0);
dragging.move(canvasEvent({ x: 230, y: 360 }));
dragging.hover({ element: task1, gfx: task1Gfx });
dragging.move(canvasEvent({ x: 248, y: 382 }));
var ctx = dragging.context();
var context = ctx.data.context;
var connectionPreview = context.getConnection(
context.allowed,
context.source,
context.target
);
var waypointsPreview = connectionPreview.waypoints.slice();
dragging.end();
// then
expect(task1.outgoing[0]).to.exist;
expect(task1.outgoing[0].waypoints).to.deep.eql(waypointsPreview);
})
);
it('should correctly lay out connection preview on reconnect end',
inject(function(canvas, bendpointMove, dragging, elementRegistry) {
// given
var task1 = elementRegistry.get('Task_1'),
task1Gfx = canvas.getGraphics(task1),
sequenceFlow2 = elementRegistry.get('SequenceFlow_2');
// when
bendpointMove.start(canvasEvent({ x: 0, y: 0 }), sequenceFlow2, 2);
dragging.move(canvasEvent({ x: 230, y: 360 }));
dragging.hover({ element: task1, gfx: task1Gfx });
dragging.move(canvasEvent({ x: 248, y: 382 }));
var ctx = dragging.context();
var context = ctx.data.context;
var connectionPreview = context.getConnection(
context.allowed,
context.source,
context.target
);
var waypointsPreview = connectionPreview.waypoints.slice();
dragging.end();
// then
expect(task1.incoming[0]).to.exist;
expect(task1.incoming[0].waypoints).to.deep.eql(waypointsPreview);
})
);
it('should correctly lay out connection preview on inserted bendpoint move',
inject(function(bendpointMove, dragging, elementRegistry) {
// given
var task2 = elementRegistry.get('Task_2'),
sequenceFlow1 = elementRegistry.get('SequenceFlow_1');
// when
bendpointMove.start(canvasEvent({ x: 700, y: 341 }), sequenceFlow1, 1, true);
dragging.move(canvasEvent({ x: 700, y: 400 }));
var ctx = dragging.context();
var context = ctx.data.context;
var connectionPreview = context.getConnection(context.allowed);
var waypointsPreview = connectionPreview.waypoints.slice();
dragging.end();
// then
expect(task2.incoming[0]).to.exist;
expect(task2.incoming[0].waypoints).to.deep.eql(waypointsPreview);
})
);
it('should correctly lay out connection preview on existing bendpoint move',
inject(function(bendpointMove, dragging, elementRegistry) {
// given
var task2 = elementRegistry.get('Task_2'),
sequenceFlow1 = elementRegistry.get('SequenceFlow_1');
// when
bendpointMove.start(canvasEvent({ x: 934, y: 341 }), sequenceFlow1, 1);
dragging.move(canvasEvent({ x: 960, y: 340 }));
var ctx = dragging.context();
var context = ctx.data.context;
var connectionPreview = context.getConnection(context.allowed);
var waypointsPreview = connectionPreview.waypoints.slice();
dragging.end();
// then
expect(task2.incoming[0]).to.exist;
expect(task2.incoming[0].waypoints).to.deep.eql(waypointsPreview);
})
);
});
describe('connection preview with connection type replacement', function() {
var diagramXML = require('test/spec/features/modeling/behavior/ReplaceConnectionBehavior.message-sequence-flow.bpmn');
beforeEach(inject(function(dragging) {
dragging.setOptions({ manual: true });
}));
afterEach(inject(function(dragging) {
dragging.setOptions({ manual: false });
}));
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
it('should correctly lay out connection preview when reconnecting with replacement',
inject(function(canvas, bendpointMove, dragging, elementRegistry) {
// given
var participant2 = elementRegistry.get('Participant_2'),
participant2Gfx = canvas.getGraphics(participant2),
sequenceFlow1 = elementRegistry.get('SequenceFlow_1');
// when
bendpointMove.start(canvasEvent(sequenceFlow1.waypoints[1]), sequenceFlow1, 1);
dragging.move(canvasEvent({ x: participant2.x + 100, y: participant2.y + 10 }));
dragging.hover({ element: participant2, gfx: participant2Gfx });
dragging.move(canvasEvent({ x: participant2.x + 105, y: participant2.y + 10 }));
var ctx = dragging.context();
var context = ctx.data.context;
var connectionPreview = context.getConnection(context.allowed);
var waypointsPreview = connectionPreview.waypoints.slice();
dragging.end();
var newWaypoints = participant2.incoming.slice(-1)[0].waypoints;
// then
expect(newWaypoints).to.exist;
expect(newWaypoints).to.deep.eql(waypointsPreview);
})
);
});
describe('attaching event', function() {
var diagramXML = require('test/spec/features/rules/BpmnRules.attaching.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
it('should correctly lay out connection after replacement',
inject(function(elementRegistry, modeling) {
// given
var event = elementRegistry.get('IntermediateThrowEventWithConnections'),
parent = elementRegistry.get('SubProcess_1');
// when
modeling.moveElements([ event ], { x: 0, y: -90 }, parent, { attach: true });
// then
var boundaryEvent = elementRegistry.get('IntermediateThrowEventWithConnections');
expect(boundaryEvent.outgoing[0]).to.have.waypoints([
{ x: 769, y: 297 },
{ x: 769, y: 369 },
{ x: 837, y: 369 }
]);
})
);
});
});
});
// helpers //////////////////////
function toPoint(p) {
return {
x: p.x,
y: p.y
};
}
function areOnSameAxis(a, b) {
return a.x === b.x || a.y === b.y;
}
================================================
FILE: test/spec/features/modeling/layout/LayoutDataAssociationSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import modelingModule from 'lib/features/modeling';
import coreModule from 'lib/core';
describe('features/modeling - layout data association', function() {
var diagramXML = require('../../../../fixtures/bpmn/basic.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: [
coreModule,
modelingModule
]
}));
var rootShape,
taskShape;
beforeEach(inject(function(elementRegistry, canvas) {
rootShape = canvas.getRootElement();
taskShape = elementRegistry.get('Task_1');
}));
it('should layout straight after DataObjectReference creation', inject(function(modeling) {
// when
var dataObjectShape = modeling.createShape({ type: 'bpmn:DataObjectReference' }, { x: 200, y: 400 }, rootShape);
modeling.connect(dataObjectShape, taskShape);
var waypoints = dataObjectShape.outgoing[0].waypoints;
// then
expect(waypoints).to.eql([
{ original: { x: 200, y: 400 }, x: 218, y: 375 },
{ original: { x: 403, y: 120 }, x: 374, y: 160 }
]);
}));
it('should layout straight after DataObjectReference move', inject(function(modeling) {
// given
var dataObjectShape = modeling.createShape({ type: 'bpmn:DataObjectReference' }, { x: 200, y: 400 }, rootShape);
modeling.connect(dataObjectShape, taskShape);
// when
modeling.moveElements([ dataObjectShape ], { x: 20, y: 0 }, rootShape);
var waypoints = dataObjectShape.outgoing[0].waypoints;
// then
expect(waypoints).to.eql([
{ original: { x: 220, y: 400 }, x: 236, y: 375 },
{ original: { x: 403, y: 120 }, x: 377, y: 160 }
]);
}));
it('should retain waypoints after DataObjectReference move', inject(function(modeling) {
// given
var dataObjectShape = modeling.createShape({ type: 'bpmn:DataObjectReference' }, { x: 200, y: 400 }, rootShape),
connection = modeling.connect(dataObjectShape, taskShape),
waypoints = connection.waypoints;
// add a waypoint
waypoints.splice(1, 0, { x: 400, y: 300 });
modeling.updateWaypoints(connection, waypoints);
// when
modeling.moveElements([ dataObjectShape ], { x: 20, y: 0 }, rootShape);
waypoints = taskShape.incoming[0].waypoints;
// then
expect(waypoints).to.eql([
{ original: { x: 220, y: 400 }, x: 238, y: 390 },
{ x: 400, y: 300 },
{ original: { x: 403, y: 120 }, x: 402, y: 160 }
]);
}));
});
================================================
FILE: test/spec/features/modeling/layout/LayoutMessageFlowSpec.bpmn
================================================
================================================
FILE: test/spec/features/modeling/layout/LayoutMessageFlowSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import modelingModule from 'lib/features/modeling';
import coreModule from 'lib/core';
describe('features/modeling - layout message flows', function() {
var diagramXML = require('./LayoutMessageFlowSpec.bpmn');
var testModules = [ coreModule, modelingModule ];
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
it('should layout manhattan after Task move', inject(function(elementRegistry, modeling) {
// given
var taskShape = elementRegistry.get('Task_A'),
messageFlowConnection = elementRegistry.get('MessageFlow_4');
// when
modeling.moveElements([ taskShape ], { x: 30, y: 20 });
// then
// expect cropped, repaired manhattan connection
expect(messageFlowConnection).to.have.waypoints([
{ original: { x: 420, y: 234 }, x: 420, y: 234 },
{ x: 420, y: 387 },
{ x: 318, y: 387 },
{ original: { x: 318, y: 448 }, x: 318, y: 448 }
]);
}));
it('should layout Task -> Participant straight after Task move',
inject(function(elementRegistry, modeling) {
// given
var taskShape = elementRegistry.get('Task_B'),
messageFlowConnection = elementRegistry.get('MessageFlow_1');
// when
modeling.moveElements([ taskShape ], { x: 20, y: -20 });
// then
// expect cropped, repaired manhattan connection
expect(messageFlowConnection).to.have.waypoints([
{ original: { x: 610, y: 194 }, x: 610, y: 194 },
{ original: { x: 610, y: 415 }, x: 610, y: 415 }
]);
})
);
it('should layout straight after Participant move', inject(function(elementRegistry, modeling) {
// given
var participantShape = elementRegistry.get('Participant_B'),
messageFlowConnection = elementRegistry.get('MessageFlow_5');
// when
modeling.moveElements([ participantShape ], { x: 100, y: 50 });
// then
// expect cropped, repaired manhattan connection
expect(messageFlowConnection).to.have.waypoints([
{ original: { x: 671, y: 214 }, x: 671, y: 214 },
{ original: { x: 671, y: 465 }, x: 671, y: 465 }
]);
}));
it('should layout EndEvent -> Participant manhattan',
inject(function(elementRegistry, modeling) {
// given
var participantShape = elementRegistry.get('Participant_B'),
messageFlowConnection = elementRegistry.get('MessageFlow_5');
// when
modeling.moveElements([ participantShape ], { x: -200, y: 0 });
// then
// expect cropped, repaired manhattan connection
expect(messageFlowConnection).to.have.waypoints([
{ original: { x: 671, y: 214 }, x: 671, y: 214 },
{ x: 671, y: 315 },
{ x: 471, y: 315 },
{ original: { x: 471, y: 415 }, x: 471, y: 415 }
]);
})
);
it('should layout SubProcess -> SubProcess (straight) on SubProcess move',
inject(function(elementRegistry, modeling) {
// given
var subProcessShape = elementRegistry.get('SubProcess_G'),
messageFlowConnection = elementRegistry.get('MessageFlow_3');
// when
modeling.moveElements([ subProcessShape ], { x: 300, y: 0 });
// then
expect(messageFlowConnection).to.have.waypoints([
{ x: 902, y: 266 }, { x: 902, y: 458 }
]);
})
);
describe('should keep task docking', function() {
describe('on SubProcess resize', function() {
it('SubProcess -> Task (straight)',
inject(function(elementRegistry, modeling) {
// given
var subProcessShape = elementRegistry.get('SubProcess_G'),
messageFlowConnection = elementRegistry.get('MessageFlow_7');
// when
modeling.resizeShape(subProcessShape, {
x: 586,
y: 458,
width: 212,
height: 122
});
// then
// expect cropped, repaired manhattan connection
expect(messageFlowConnection).to.have.waypoints([
{ x: 752, y: 458 },
{ x: 752, y: 214 }
]);
})
);
});
describe('on SubProcess move', function() {
it('SubProcess -> Task (straight)',
inject(function(elementRegistry, modeling) {
// given
var subProcessShape = elementRegistry.get('SubProcess_G'),
messageFlowConnection = elementRegistry.get('MessageFlow_7');
// when
modeling.moveElements([ subProcessShape ], { x: 50, y: 0 });
// then
// expect cropped, repaired manhattan connection
expect(messageFlowConnection).to.have.waypoints([
{ x: 752, y: 458 },
{ x: 752, y: 214 }
]);
})
);
});
describe('on Participant move', function() {
it('Task -> Participant (straight)',
inject(function(elementRegistry, modeling) {
// given
var participantShape = elementRegistry.get('Participant_B'),
messageFlowConnection_1 = elementRegistry.get('MessageFlow_1'),
messageFlowConnection_6 = elementRegistry.get('MessageFlow_6');
// when
modeling.moveElements([ participantShape ], { x: 300, y: 50 });
// then
// expect cropped, repaired manhattan connection
expect(messageFlowConnection_1).to.have.waypoints([
{ original: { x: 590, y: 214 }, x: 590, y: 214 },
{ original: { x: 590, y: 465 }, x: 590, y: 465 }
]);
expect(messageFlowConnection_6).to.have.waypoints([
{ original: { x: 773, y: 465 }, x: 773, y: 465 },
{ original: { x: 773, y: 214 }, x: 773, y: 214 }
]);
})
);
});
describe('on Participant resize', function() {
it('Task -> Participant (straight)',
inject(function(elementRegistry, modeling) {
// given
var participantShape = elementRegistry.get('Participant_B'),
messageFlowConnection = elementRegistry.get('MessageFlow_1');
// when
modeling.resizeShape(participantShape, {
x: 222,
y: 415,
width: 580,
height: 185
});
// then
// expect cropped, repaired manhattan connection
expect(messageFlowConnection).to.have.waypoints([
{ x: 590, y: 214 },
{ x: 590, y: 415 }
]);
})
);
it('Participant -> Task (straight)',
inject(function(elementRegistry, modeling) {
// given
var participantShape = elementRegistry.get('Participant_B'),
messageFlowConnection = elementRegistry.get('MessageFlow_6');
// when
modeling.resizeShape(participantShape, {
x: 222,
y: 415,
width: 580,
height: 185
});
// then
// expect cropped, repaired manhattan connection
expect(messageFlowConnection).to.have.waypoints([
{ x: 773, y: 415 },
{ x: 773, y: 214 }
]);
})
);
it('Task -> Participant (manhattan)',
inject(function(elementRegistry, modeling) {
// given
var participantShape = elementRegistry.get('Participant_B'),
messageFlowConnection = elementRegistry.get('MessageFlow_1');
// when
modeling.resizeShape(participantShape, {
x: 622,
y: 415,
width: 600,
height: 185
});
// then
// expect cropped, repaired manhattan connection
expect(messageFlowConnection).to.have.waypoints([
{ x: 590, y: 214 },
{ x: 590, y: 315 },
{ x: 990, y: 315 },
{ x: 990, y: 415 }
]);
})
);
it('Participant -> Task (manhattan)',
inject(function(elementRegistry, modeling) {
// given
var participantShape = elementRegistry.get('Participant_B'),
messageFlowConnection = elementRegistry.get('MessageFlow_6');
// when
modeling.resizeShape(participantShape, {
x: 222,
y: 415,
width: 500,
height: 185
});
// then
// expect cropped, repaired manhattan connection
expect(messageFlowConnection).to.have.waypoints([
{ x: 681, y: 415 },
{ x: 681, y: 315 },
{ x: 773, y: 315 },
{ x: 773, y: 214 }
]);
})
);
});
});
});
describe('features/modeling - vertical layout message flows', function() {
var diagramXML = require('./LayoutMessageFlowSpec.vertical.bpmn');
var testModules = [ coreModule, modelingModule ];
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
it('should layout manhattan after Task move', inject(function(elementRegistry, modeling) {
// given
var taskShape = elementRegistry.get('Task_A'),
messageFlowConnection = elementRegistry.get('MessageFlow_4');
// when
modeling.moveElements([ taskShape ], { x: 20, y: 30 });
// then
// expect cropped, repaired manhattan connection
expect(messageFlowConnection).to.have.waypoints([
{ x: 244, y: 420 },
{ x: 387, y: 420 },
{ x: 387, y: 318 },
{ x: 448, y: 318 }
]);
}));
it('should layout Task -> Participant straight after Task move',
inject(function(elementRegistry, modeling) {
// given
var taskShape = elementRegistry.get('Task_B'),
messageFlowConnection = elementRegistry.get('MessageFlow_1');
// when
modeling.moveElements([ taskShape ], { x: -20, y: 20 });
// then
// expect cropped, repaired manhattan connection
expect(messageFlowConnection).to.have.waypoints([
{ x: 204, y: 600 },
{ x: 415, y: 600 }
]);
})
);
it('should layout straight after Participant move', inject(function(elementRegistry, modeling) {
// given
var participantShape = elementRegistry.get('Participant_B'),
messageFlowConnection = elementRegistry.get('MessageFlow_5');
// when
modeling.moveElements([ participantShape ], { x: 50, y: 100 });
// then
// expect cropped, repaired manhattan connection
expect(messageFlowConnection).to.have.waypoints([
{ x: 214, y: 671 },
{ x: 465, y: 671 }
]);
}));
it('should layout EndEvent -> Participant manhattan',
inject(function(elementRegistry, modeling) {
// given
var participantShape = elementRegistry.get('Participant_B'),
messageFlowConnection = elementRegistry.get('MessageFlow_5');
// when
modeling.moveElements([ participantShape ], { x: 0, y: -200 });
// then
// expect cropped, repaired manhattan connection
expect(messageFlowConnection).to.have.waypoints([
{ x: 214, y: 671 },
{ x: 315, y: 671 },
{ x: 315, y: 471 },
{ x: 415, y: 471 }
]);
})
);
it('should layout SubProcess -> SubProcess (straight) on SubProcess move',
inject(function(elementRegistry, modeling) {
// given
var subProcessShape = elementRegistry.get('SubProcess_G'),
messageFlowConnection = elementRegistry.get('MessageFlow_3');
// when
modeling.moveElements([ subProcessShape ], { x: 0, y: 300 });
// then
expect(messageFlowConnection).to.have.waypoints([
{ x: 266, y: 902 },
{ x: 458, y: 902 }
]);
})
);
describe('should keep task docking', function() {
describe('on SubProcess resize', function() {
it('SubProcess -> Task (straight)',
inject(function(elementRegistry, modeling) {
// given
var subProcessShape = elementRegistry.get('SubProcess_G'),
messageFlowConnection = elementRegistry.get('MessageFlow_7');
// when
modeling.resizeShape(subProcessShape, {
x: 458,
y: 586,
width: 122,
height: 212
});
// then
// expect cropped, repaired manhattan connection
expect(messageFlowConnection).to.have.waypoints([
{ x: 458, y: 752 },
{ x: 224, y: 752 }
]);
})
);
});
describe('on SubProcess move', function() {
it('SubProcess -> Task (straight)',
inject(function(elementRegistry, modeling) {
// given
var subProcessShape = elementRegistry.get('SubProcess_G'),
messageFlowConnection = elementRegistry.get('MessageFlow_7');
// when
modeling.moveElements([ subProcessShape ], { x: 0, y: 50 });
// then
// expect cropped, repaired manhattan connection
expect(messageFlowConnection).to.have.waypoints([
{ x: 458, y: 752 },
{ x: 224, y: 752 }
]);
})
);
});
describe('on Participant move', function() {
it('Task -> Participant (straight)',
inject(function(elementRegistry, modeling) {
// given
var participantShape = elementRegistry.get('Participant_B'),
messageFlowConnection_1 = elementRegistry.get('MessageFlow_1'),
messageFlowConnection_6 = elementRegistry.get('MessageFlow_6');
// when
modeling.moveElements([ participantShape ], { x: 50, y: 300 });
// then
// expect cropped, repaired manhattan connection
expect(messageFlowConnection_1).to.have.waypoints([
{ x: 224, y: 580 },
{ x: 465, y: 580 }
]);
expect(messageFlowConnection_6).to.have.waypoints([
{ x: 465, y: 773 },
{ x: 224, y: 773 }
]);
})
);
});
describe('on Participant resize', function() {
it('Task -> Participant (straight)',
inject(function(elementRegistry, modeling) {
// given
var participantShape = elementRegistry.get('Participant_B'),
messageFlowConnection = elementRegistry.get('MessageFlow_1');
// when
modeling.resizeShape(participantShape, {
x: 415,
y: 222,
width: 185,
height: 580
});
// then
// expect cropped, repaired manhattan connection
expect(messageFlowConnection).to.have.waypoints([
{ x: 224, y: 580 },
{ x: 415, y: 580 }
]);
})
);
it('Participant -> Task (straight)',
inject(function(elementRegistry, modeling) {
// given
var participantShape = elementRegistry.get('Participant_B'),
messageFlowConnection = elementRegistry.get('MessageFlow_6');
// when
modeling.resizeShape(participantShape, {
x: 415,
y: 222,
width: 185,
height: 580
});
// then
// expect cropped, repaired manhattan connection
expect(messageFlowConnection).to.have.waypoints([
{ x: 415, y: 773 },
{ x: 224, y: 773 }
]);
})
);
it('Task -> Participant (manhattan)',
inject(function(elementRegistry, modeling) {
// given
var participantShape = elementRegistry.get('Participant_B'),
messageFlowConnection = elementRegistry.get('MessageFlow_1');
// when
modeling.resizeShape(participantShape, {
x: 415,
y: 622,
width: 185,
height: 600
});
// then
// expect cropped, repaired manhattan connection
expect(messageFlowConnection).to.have.waypoints([
{ x: 224, y: 580 },
{ x: 320, y: 580 },
{ x: 320, y: 980 },
{ x: 415, y: 980 }
]);
})
);
it('Participant -> Task (manhattan)',
inject(function(elementRegistry, modeling) {
// given
var participantShape = elementRegistry.get('Participant_B'),
messageFlowConnection = elementRegistry.get('MessageFlow_6');
// when
modeling.resizeShape(participantShape, {
x: 415,
y: 222,
width: 185,
height: 500
});
// then
// expect cropped, repaired manhattan connection
expect(messageFlowConnection).to.have.waypoints([
{ x: 415, y: 681 },
{ x: 320, y: 681 },
{ x: 320, y: 773 },
{ x: 224, y: 773 }
]);
})
);
});
});
});
================================================
FILE: test/spec/features/modeling/layout/LayoutMessageFlowSpec.vertical.bpmn
================================================
================================================
FILE: test/spec/features/modeling/layout/LayoutSequenceFlowSpec.boundaryEvents.bpmn
================================================
================================================
FILE: test/spec/features/modeling/layout/LayoutSequenceFlowSpec.boundaryEventsLoops.bpmn
================================================
================================================
FILE: test/spec/features/modeling/layout/LayoutSequenceFlowSpec.flowElements.bpmn
================================================
SequenceFlow_1
SequenceFlow_2
SequenceFlow_3
SequenceFlow_4
SequenceFlow_1
SequenceFlow_2
SequenceFlow_3
SequenceFlow_4
================================================
FILE: test/spec/features/modeling/layout/LayoutSequenceFlowSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import {
connect,
element,
move,
reconnectEnd
} from './Helper';
import { getMid } from 'diagram-js/lib/layout/LayoutUtil';
import modelingModule from 'lib/features/modeling';
import coreModule from 'lib/core';
describe('features/modeling - layout', function() {
describe('boundary events', function() {
describe('loops', function() {
var diagramXML = require('./LayoutSequenceFlowSpec.boundaryEventsLoops.bpmn');
var testModules = [ coreModule, modelingModule ];
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
describe('in the corner', function() {
it('attached top right', function() {
// when
var connection = connect('BoundaryEvent_TopRight', 'SubProcess');
// then
expect(connection).to.have.waypoints([
{ original: { x: 550, y: 200 }, x: 550, y: 182 },
{ x: 550, y: 162 },
{ x: 375, y: 162 },
{ original: { x: 375, y: 300 }, x: 375, y: 200 }
]);
});
it('attached bottom right', function() {
// when
var connection = connect('BoundaryEvent_BottomRight', 'SubProcess');
// then
expect(connection).to.have.waypoints([
{ original: { x: 550, y: 368 } , x: 568, y: 368 },
{ x: 588, y: 368 },
{ x: 588, y: 300 },
{ original: { x: 375, y: 300 } , x: 550, y: 300 }
]);
});
it('attached bottom left', function() {
// when
var connection = connect('BoundaryEvent_BottomLeft', 'SubProcess');
// then
expect(connection).to.have.waypoints([
{ original: { x: 200, y: 500 }, x: 200, y: 418 },
{ x: 200, y: 438 },
{ x: 375, y: 438 },
{ original: { x: 375, y: 300 }, x: 375, y: 400 }
]);
});
it('attached top left', function() {
// when
var connection = connect('BoundaryEvent_TopLeft', 'SubProcess');
// then
expect(connection).to.have.waypoints([
{ original: { x: 200, y: 238 }, x: 182, y: 238 },
{ x: 162, y: 238 },
{ x: 162, y: 300 },
{ original: { x: 375, y: 300 }, x: 200, y: 300 }
]);
});
});
describe('on the side center', function() {
var host = 'SubProcess_2';
it('attached top center', function() {
// when
var connection = connect('BoundaryEvent_TopCenter', host);
// then
expect(connection).to.have.waypoints([
{ original: { x: 375, y: 460 }, x: 375, y: 442 },
{ x:375, y: 422 },
{ x:180, y: 422 },
{ x:180, y: 560 },
{ original:{ x: 375, y: 560 }, x: 200, y: 560 }
]);
});
it('attached center right', function() {
// when
var connection = connect('BoundaryEvent_CenterRight', host);
// then
expect(connection).to.have.waypoints([
{ original: { x: 550, y: 560 }, x: 568, y: 560 },
{ x: 588, y: 560 },
{ x: 588, y: 680 },
{ x: 375, y: 680 },
{ original: { x: 375, y: 560 }, x: 375, y: 660 }
]);
});
it('attached bottom center', function() {
// when
var connection = connect('BoundaryEvent_BottomCenter', host);
// then
expect(connection).to.have.waypoints([
{ original: { x: 375, y: 660 }, x: 375, y: 678 },
{ x: 375, y: 698 },
{ x: 180, y: 698 },
{ x: 180, y: 560 },
{ original: { x: 375, y: 560 }, x: 200, y: 560 }
]);
});
it('attached center left', function() {
// when
var connection = connect('BoundaryEvent_CenterLeft', host);
// then
expect(connection).to.have.waypoints([
{ original: { x: 200, y: 560 }, x: 182, y: 560 },
{ x: 162, y: 560 },
{ x: 162, y: 680 },
{ x: 375, y: 680 },
{ original: { x: 375, y: 560 }, x: 375, y: 660 }
]);
});
});
});
describe('non-loops', function() {
var diagramXML = require('./LayoutSequenceFlowSpec.boundaryEvents.bpmn');
var testModules = [ coreModule, modelingModule ];
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
it('attached top right, orientation top', function() {
// when
var connection = connect('BoundaryEvent_TopRight', 'Task_Top');
// then
expect(connection).to.have.waypoints([
{ original: { x: 650, y: 300 }, x: 650, y: 282 },
{ x: 650, y: 40 },
{ original: { x: 450, y: 40 }, x: 500, y: 40 }
]);
});
it('attached top right, orientation right', function() {
// when
var connection = connect('BoundaryEvent_TopRight', 'Task_Right');
// then
expect(connection).to.have.waypoints([
{ original: { x: 650, y: 300 }, x: 668, y: 300 },
{ x: 900, y: 300 },
{ original: { x: 900, y: 390 }, x: 900, y: 350 }
]);
});
it('attached top right, orientation bottom', function() {
// when
var connection = connect('BoundaryEvent_TopRight', 'Task_Bottom');
// then
expect(connection).to.have.waypoints([
{ original: { x: 650, y: 300 }, x: 650, y: 282 },
{ x: 650, y: 262 },
{ x: 450, y: 262 },
{ original: { x: 450, y: 690 }, x: 450, y: 650 }
]);
});
it('attached top right, orientation left', function() {
// when
var connection = connect('BoundaryEvent_TopRight', 'Task_Left');
// then
expect(connection).to.have.waypoints([
{ original: { x: 650, y: 300 }, x: 650, y: 282 },
{ x: 650, y: 262 },
{ x: 50, y: 262 },
{ original: { x: 50, y: 390 }, x: 50, y: 350 }
]);
});
it('attached bottom center, orientation bottom', function() {
// when
var connection = connect('BoundaryEvent_BottomCenter', 'Task_Bottom');
expect(connection).to.have.waypoints([
{ x: 450, y: 518 },
{ x: 450, y: 650 }
]);
});
it('attached top center, orientation top', function() {
// when
var connection = connect('BoundaryEvent_TopCenter', 'Task_Top');
expect(connection).to.have.waypoints([
{ x: 450, y: 282 },
{ x: 450, y: 80 }
]);
});
it('attached right center, orientation right', function() {
// when
var connection = connect('BoundaryEvent_RightCenter', 'Task_Right');
expect(connection).to.have.waypoints([
{ x: 668, y: 390 },
{ x: 850, y: 390 }
]);
});
it('attached right center, orientation left', function() {
// when
var connection = connect('BoundaryEvent_RightCenter', 'Task_Left');
expect(connection).to.have.waypoints([
{ x: 668, y: 390 },
{ x: 688, y: 390 },
{ x: 688, y: 410 },
{ x: 536, y: 410 },
{ x: 536, y: 390 },
{ x: 100, y: 390 }
]);
});
it('should layout straight for axis-aligned corner boundary event', inject(
function(elementRegistry, modeling) {
// given
// BoundaryEvent_BottomRightCorner is on the bottom-right corner of
// Task_CornerBoundary (mid x=909, y=389 — outside the contracted task
// bounds right=890, bottom=370 — giving 'bottom-right' orientation).
// Task_CornerTarget center x=909 matches exactly, so getOrientation
// returns strict 'bottom' with no horizontal component, triggering the
// early return in getBoundaryEventTargetLayout → layout 'b:v'.
var boundaryEvent = elementRegistry.get('BoundaryEvent_BottomRightCorner'),
targetTask = elementRegistry.get('Task_CornerTarget');
// when
var connection = modeling.connect(boundaryEvent, targetTask);
// then - straight vertical line, no extra waypoints
expect(connection).to.have.waypoints([
{ x: 909, y: 407 },
{ x: 909, y: 480 }
]);
}
));
it('should layout straight for horizontally axis-aligned corner boundary event', inject(
function(elementRegistry, modeling) {
// given
// BoundaryEvent_TopRightCorner is on the top-right corner of
// Task_CornerBoundaryH (mid x=909, y=109 — outside the contracted task
// bounds right=890, top=110 — giving 'top-right' orientation).
// Task_CornerTargetH center y=109 matches exactly, so getOrientation
// returns strict 'right' with no vertical component, triggering the
// early return in getBoundaryEventTargetLayout → layout 'r:h'.
var boundaryEvent = elementRegistry.get('BoundaryEvent_TopRightCorner'),
targetTask = elementRegistry.get('Task_CornerTargetH');
// when
var connection = modeling.connect(boundaryEvent, targetTask);
// then - straight horizontal line, no extra waypoints
expect(connection).to.have.waypoints([
{ x: 927, y: 109 },
{ x: 1000, y: 109 }
]);
}
));
});
});
describe('vertical boundary events', function() {
describe('loops', function() {
var diagramXML = require('./LayoutSequenceFlowSpec.vertical.boundaryEventsLoops.bpmn');
var testModules = [ coreModule, modelingModule ];
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
describe('in the corner', function() {
it('attached top right', function() {
// when
var connection = connect('BoundaryEvent_TopRight', 'SubProcess');
// then
expect(connection).to.have.waypoints([
{ x: 628, y: 170 },
{ x: 648, y: 170 },
{ x: 648, y: 270 },
{ x: 610, y: 270 }
]);
});
it('attached bottom right', function() {
// when
var connection = connect('BoundaryEvent_BottomRight', 'SubProcess');
// then
expect(connection).to.have.waypoints([
{ x: 628, y: 338 },
{ x: 648, y: 338 },
{ x: 648, y: 270 },
{ x: 610, y: 270 }
]);
});
it('attached bottom left', function() {
// when
var connection = connect('BoundaryEvent_BottomLeft', 'SubProcess');
// then
expect(connection).to.have.waypoints([
{ x: 242, y: 370 },
{ x: 222, y: 370 },
{ x: 222, y: 270 },
{ x: 260, y: 270 }
]);
});
it('attached top left', function() {
// when
var connection = connect('BoundaryEvent_TopLeft', 'SubProcess');
// then
expect(connection).to.have.waypoints([
{ x: 242, y: 208 },
{ x: 222, y: 208 },
{ x: 222, y: 270 },
{ x: 260, y: 270 }
]);
});
});
describe('on the side center', function() {
var host = 'SubProcess_2';
it('attached top center', function() {
// when
var connection = connect('BoundaryEvent_TopCenter', host);
// then
expect(connection).to.have.waypoints([
{ x: 435, y: 412 },
{ x: 435, y: 392 },
{ x: 630, y: 392 },
{ x: 630, y: 530 },
{ x: 610, y: 530 }
]);
});
it('attached center right', function() {
// when
var connection = connect('BoundaryEvent_CenterRight', host);
// then
expect(connection).to.have.waypoints([
{ x: 628, y: 530 },
{ x: 648, y: 530 },
{ x: 648, y: 410 },
{ x: 435, y: 410 },
{ x: 435, y: 430 }
]);
});
it('attached bottom center', function() {
// when
var connection = connect('BoundaryEvent_BottomCenter', host);
// then
expect(connection).to.have.waypoints([
{ x: 435, y: 648 },
{ x: 435, y: 668 },
{ x: 630, y: 668 },
{ x: 630, y: 530 },
{ x: 610, y: 530 }
]);
});
it('attached center left', function() {
// when
var connection = connect('BoundaryEvent_CenterLeft', host);
// then
expect(connection).to.have.waypoints([
{ x: 242, y: 530 },
{ x: 222, y: 530 },
{ x: 222, y: 410 },
{ x: 435, y: 410 },
{ x: 435, y: 430 }
]);
});
});
});
describe('non-loops', function() {
var diagramXML = require('./LayoutSequenceFlowSpec.vertical.boundaryEvents.bpmn');
var testModules = [ coreModule, modelingModule ];
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
it('attached bottom left, orientation left', function() {
// when
var connection = connect('BoundaryEvent_BottomLeft', 'Task_Left');
// then
expect(connection).to.have.waypoints([
{ x: 502, y: 740 },
{ x: 260, y: 740 },
{ x: 260, y: 580 }
]);
});
it('attached bottom left, orientation bottom', function() {
// when
var connection = connect('BoundaryEvent_BottomLeft', 'Task_Bottom');
// then
expect(connection).to.have.waypoints([
{ x: 520, y: 758 },
{ x: 520, y: 990 },
{ x: 560, y: 990 }
]);
});
it('attached bottom left, orientation right', function() {
// when
var connection = connect('BoundaryEvent_BottomLeft', 'Task_Right');
// then
expect(connection).to.have.waypoints([
{ x: 502, y: 740 },
{ x: 482, y: 740 },
{ x: 482, y: 540 },
{ x: 860, y: 540 }
]);
});
it('attached bottom left, orientation top', function() {
// when
var connection = connect('BoundaryEvent_BottomLeft', 'Task_Top');
// then
expect(connection).to.have.waypoints([
{ x: 502, y: 740 },
{ x: 482, y: 740 },
{ x: 482, y: 140 },
{ x: 560, y: 140 }
]);
});
it('attached right center, orientation right', function() {
// when
var connection = connect('BoundaryEvent_RightCenter', 'Task_Right');
expect(connection).to.have.waypoints([
{ x: 738, y: 540 },
{ x: 860, y: 540 }
]);
});
it('attached left center, orientation left', function() {
// when
var connection = connect('BoundaryEvent_LeftCenter', 'Task_Left');
expect(connection).to.have.waypoints([
{ x: 502, y: 540 },
{ x: 310, y: 540 }
]);
});
it('attached bottom center, orientation bottom', function() {
// when
var connection = connect('BoundaryEvent_BottomCenter', 'Task_Bottom');
expect(connection).to.have.waypoints([
{ x: 610, y: 758 },
{ x: 610, y: 950 }
]);
});
it('attached bottom center, orientation top', function() {
// when
var connection = connect('BoundaryEvent_BottomCenter', 'Task_Top');
expect(connection).to.have.waypoints([
{ x: 610, y: 758 },
{ x: 610, y: 778 },
{ x: 630, y: 778 },
{ x: 630, y: 624 },
{ x: 610, y: 624 },
{ x: 610, y: 180 }
]);
});
});
});
describe('flow elements', function() {
var diagramXML = require('./LayoutSequenceFlowSpec.flowElements.bpmn');
var testModules = [ coreModule, modelingModule ];
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
describe('loops', function() {
it('should layout loop', function() {
// when
var connection = connect('Task_1', 'Task_1');
// then
expect(connection).to.have.waypoints([
{ x: 332, y: 260 },
{ x: 332, y: 280 },
{ x: 262, y: 280 },
{ x: 262, y: 220 },
{ x: 282, y: 220 }
]);
});
it('should NOT relayout loop', inject(function(elementRegistry) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow_1'),
task = elementRegistry.get('Task_1');
// when
reconnectEnd(sequenceFlow, task, getMid(task));
// then
expect(sequenceFlow).to.have.waypoints([
{ x: 382, y: 241 },
{ x: 559, y: 241 },
{ x: 559, y: 220 },
{ x: 382, y: 220 }
]);
}));
it('should relayout loop (b:l)', inject(function(elementRegistry) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow_2'),
task = elementRegistry.get('Task_1');
// when
reconnectEnd(sequenceFlow, task, getMid(task));
// then
expect(sequenceFlow).to.have.waypoints([
{ x: 332, y: 260 },
{ x: 332, y: 280 },
{ x: 262, y: 280 },
{ x: 262, y: 220 },
{ x: 282, y: 220 }
]);
}));
it('should relayout loop (l:t)', inject(function(elementRegistry) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow_3'),
task = elementRegistry.get('Task_1');
// when
reconnectEnd(sequenceFlow, task, getMid(task));
// then
expect(sequenceFlow).to.have.waypoints([
{ x: 282, y: 220 },
{ x: 262, y: 220 },
{ x: 262, y: 160 },
{ x: 332, y: 160 },
{ x: 332, y: 180 }
]);
}));
it('should relayout loop (t:r)', inject(function(elementRegistry) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow_4'),
task = elementRegistry.get('Task_1');
// when
reconnectEnd(sequenceFlow, task, getMid(task));
// then
expect(sequenceFlow).to.have.waypoints([
{ x: 332, y: 180 },
{ x: 332, y: 160 },
{ x: 402, y: 160 },
{ x: 402, y: 220 },
{ x: 382, y: 220 }
]);
}));
});
describe('gateway layout', function() {
it('should layout v:h after Gateway', inject(function() {
// when
var connection = connect('ExclusiveGateway_1', 'BusinessRuleTask_1');
// then
expect(connection).to.have.waypoints([
{ original: { x: 678, y: 302 }, x: 678, y: 277 },
{ x: 678, y: 220 },
{ original: { x: 840, y: 220 }, x: 790, y: 220 }
]);
}));
it('should layout h:v before Gateway', inject(function() {
// when
var connection = connect('BusinessRuleTask_1', 'ParallelGateway_1');
// then
expect(connection).to.have.waypoints([
{ original: { x: 840, y: 220 }, x: 890, y: 220 },
{ x: 1005, y: 220 },
{ original: { x: 1005, y: 302 }, x: 1005, y: 277 }
]);
}));
});
describe('other elements layout', function() {
it('should layout h:h after StartEvent', inject(function() {
// when
var connection = connect('StartEvent_1', 'Task_1');
// then
expect(connection).to.have.waypoints([
{ original: { x: 170, y: 302 }, x: 188, y: 302 },
{ x: 235, y: 302 },
{ x: 235, y: 220 },
{ original: { x: 332, y: 220 }, x: 282, y: 220 }
]);
}));
it('should layout h:h after Task', inject(function() {
// when
var connection = connect('ServiceTask_1', 'BusinessRuleTask_1');
// then
expect(connection).to.have.waypoints([
{ original: { x: 678, y: 117 }, x: 728, y: 117 },
{ x: 759, y: 117 },
{ x: 759, y: 220 },
{ original: { x: 840, y: 220 }, x: 790, y: 220 }
]);
}));
it('should layout h:h after IntermediateEvent', inject(function() {
// when
var connection = connect('IntermediateThrowEvent_1', 'ServiceTask_1');
// then
expect(connection).to.have.waypoints([
{ original: { x: 496, y: 302 }, x: 514, y: 302 },
{ x: 571, y: 302 },
{ x: 571, y: 117 },
{ original: { x: 678, y: 117 }, x: 628, y: 117 }
]);
}));
it('should layout h:h after IntermediateEvent (right to left)', inject(function() {
// when
var connection = connect('IntermediateThrowEvent_1', 'Task_1');
// then
expect(connection).to.have.waypoints([
{ original: { x: 496, y: 302 }, x: 478, y: 302 },
{ x: 430, y: 302 },
{ x: 430, y: 220 },
{ original: { x: 332, y: 220 }, x: 382, y: 220 }
]);
}));
});
describe('relayout', function() {
it('should repair after reconnect end', inject(function() {
// given
var newDocking = { x: 660, y: 300 };
var connection = element('SequenceFlow_1');
// when
reconnectEnd(connection, 'ExclusiveGateway_1', newDocking);
// then
expect(connection).to.have.waypoints([
{ x: 382, y: 241 },
{ x: 559, y: 241 },
{ x: 559, y: 300 },
{ x: 655, y: 300 }
]);
}));
it('should repair after target move', inject(function() {
// given
var delta = { x: -30, y: 20 };
var connection = element('SequenceFlow_1');
// when
move('ServiceTask_1', delta);
// then
expect(connection).to.have.waypoints([
{ x: 382, y: 241 },
{ x: 559, y: 241 },
{ x: 559, y: 158 },
{ x: 598, y: 158 }
]);
}));
it('should repair after source move', inject(function() {
// given
var delta = { x: -30, y: 20 };
var connection = element('SequenceFlow_1');
// when
move('Task_1', delta);
// then
expect(connection).to.have.waypoints([
{ x: 352, y: 261 },
{ x: 559, y: 261 },
{ x: 559, y: 138 },
{ x: 628, y: 138 }
]);
}));
});
});
describe('vertical flow elements', function() {
var diagramXML = require('./LayoutSequenceFlowSpec.vertical.flowElements.bpmn');
var testModules = [ coreModule, modelingModule ];
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
describe('loops', function() {
it('should layout loop', function() {
// when
var connection = connect('Task_1', 'Task_1');
// then
expect(connection).to.have.waypoints([
{ x: 510, y: 382 },
{ x: 510, y: 402 },
{ x: 580, y: 402 },
{ x: 580, y: 342 },
{ x: 560, y: 342 }
]);
});
it('should NOT relayout loop', inject(function(elementRegistry) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow_1'),
task = elementRegistry.get('Task_1');
// when
reconnectEnd(sequenceFlow, task, getMid(task));
// then
expect(sequenceFlow).to.have.waypoints([
{ x: 531, y: 382 },
{ x: 531, y: 569 },
{ x: 510, y: 569 },
{ x: 510, y: 382 }
]);
}));
it('should relayout loop (r:t)', inject(function(elementRegistry) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow_2'),
task = elementRegistry.get('Task_1');
// when
reconnectEnd(sequenceFlow, task, getMid(task));
// then
expect(sequenceFlow).to.have.waypoints([
{ x: 560, y: 342 },
{ x: 580, y: 342 },
{ x: 580, y: 282 },
{ x: 510, y: 282 },
{ x: 510, y: 302 }
]);
}));
it('should relayout loop (t:l)', inject(function(elementRegistry) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow_3'),
task = elementRegistry.get('Task_1');
// when
reconnectEnd(sequenceFlow, task, getMid(task));
// then
expect(sequenceFlow).to.have.waypoints([
{ x: 510, y: 302 },
{ x: 510, y: 282 },
{ x: 440, y: 282 },
{ x: 440, y: 342 },
{ x: 460, y: 342 }
]);
}));
it('should relayout loop (l:b)', inject(function(elementRegistry) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow_4'),
task = elementRegistry.get('Task_1');
// when
reconnectEnd(sequenceFlow, task, getMid(task));
// then
expect(sequenceFlow).to.have.waypoints([
{ x: 460, y: 342 },
{ x: 440, y: 342 },
{ x: 440, y: 402 },
{ x: 510, y: 402 },
{ x: 510, y: 382 }
]);
}));
});
describe('gateway layout', function() {
it('should layout h:v after Gateway', inject(function() {
// when
var connection = connect('ExclusiveGateway_1', 'BusinessRuleTask_1');
// then
expect(connection).to.have.waypoints([
{ x: 577, y: 688 },
{ x: 530, y: 688 },
{ x: 530, y: 810 }
]);
}));
it('should layout v:h before Gateway', inject(function() {
// when
var connection = connect('BusinessRuleTask_1', 'ParallelGateway_1');
// then
expect(connection).to.have.waypoints([
{ x: 530, y: 890 },
{ x: 530, y: 1015 },
{ x: 577, y: 1015 }
]);
}));
});
describe('other elements layout', function() {
it('should layout v:v after StartEvent', inject(function() {
// when
var connection = connect('StartEvent_1', 'Task_1');
// then
expect(connection).to.have.waypoints([
{ x: 602, y: 198 },
{ x: 602, y: 250 },
{ x: 510, y: 250 },
{ x: 510, y: 302 }
]);
}));
it('should layout v:v after Task', inject(function() {
// when
var connection = connect('ServiceTask_1', 'BusinessRuleTask_1');
// then
expect(connection).to.have.waypoints([
{ x: 407, y: 728 },
{ x: 407, y: 769 },
{ x: 530, y: 769 },
{ x: 530, y: 810 }
]);
}));
it('should layout v:v after IntermediateEvent', inject(function() {
// when
var connection = connect('IntermediateThrowEvent_1', 'ServiceTask_1');
// then
expect(connection).to.have.waypoints([
{ x: 602, y: 524 },
{ x: 602, y: 586 },
{ x: 407, y: 586 },
{ x: 407, y: 648 }
]);
}));
it('should layout v:v after IntermediateEvent (bottom to top)', inject(function() {
// when
var connection = connect('IntermediateThrowEvent_1', 'Task_1');
// then
expect(connection).to.have.waypoints([
{ x: 602, y: 488 },
{ x: 602, y: 435 },
{ x: 510, y: 435 },
{ x: 510, y: 382 }
]);
}));
});
describe('relayout', function() {
it('should repair after reconnect end', inject(function() {
// given
var newDocking = { x: 660, y: 300 };
var connection = element('SequenceFlow_1');
// when
reconnectEnd(connection, 'ExclusiveGateway_1', newDocking);
// then
expect(connection).to.have.waypoints([
{ x: 531, y: 382 },
{ x: 531, y: 569 },
{ x: 660, y: 569 },
{ x: 660, y: 300 }
]);
}));
it('should repair after target move', inject(function() {
// given
var delta = { x: 20, y: -30 };
var connection = element('SequenceFlow_1');
// when
move('ServiceTask_1', delta);
// then
expect(connection).to.have.waypoints([
{ x: 531, y: 382 },
{ x: 531, y: 569 },
{ x: 448, y: 569 },
{ x: 448, y: 618 }
]);
}));
it('should repair after source move', inject(function() {
// given
var delta = { x: 20, y: -30 };
var connection = element('SequenceFlow_1');
// when
move('Task_1', delta);
// then
expect(connection).to.have.waypoints([
{ x: 551, y: 352 },
{ x: 551, y: 569 },
{ x: 428, y: 569 },
{ x: 428, y: 648 }
]);
}));
});
});
describe('subProcess', function() {
var diagramXML = require('./LayoutSequenceFlowSpec.subProcess.bpmn');
var testModules = [ coreModule, modelingModule ];
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
it('should layout straight between subProcesses (top -> bottom)', function() {
// when
var connection = connect('SubProcess_Center', 'SubProcess_Bottom'),
source = connection.source,
target = connection.target;
var expectedX = getMid(target).x;
// then
expect(connection).to.have.waypoints([
{ x: expectedX, y: source.y + source.height },
{ x: expectedX, y: target.y }
]);
});
it('should layout straight between subProcesses (bottom -> top)', function() {
// when
var connection = connect('SubProcess_Bottom', 'SubProcess_Center'),
source = connection.source,
target = connection.target;
var expectedX = getMid(target).x;
// then
expect(connection).to.have.waypoints([
{ x: expectedX, y: source.y },
{ x: expectedX, y: target.y + target.height }
]);
});
it('should layout straight between subProcess and task next to it (subProcess -> task)',
function() {
// when
var connection = connect('SubProcess_Center', 'Task_Right'),
source = connection.source,
target = connection.target;
var expectedY = getMid(target).y;
// then
expect(connection).to.have.waypoints([
{ x: source.x + source.width, y: expectedY },
{ x: target.x, y: expectedY }
]);
}
);
it('should layout straight between subProcess and task next to it (task -> subProcess)',
function() {
// when
var connection = connect('Task_Right', 'SubProcess_Center'),
source = connection.source,
target = connection.target;
var expectedY = getMid(source).y;
// then
expect(connection).to.have.waypoints([
{ x: source.x, y: expectedY },
{ x: target.x + target.width, y: expectedY }
]);
}
);
it('should layout straight between subProcess and task above (subProcess -> task)', function() {
// when
var connection = connect('SubProcess_Center', 'Task_Top'),
source = connection.source,
target = connection.target;
var expectedX = getMid(target).x;
// then
expect(connection).to.have.waypoints([
{ x: expectedX, y: source.y },
{ x: expectedX, y: target.y + target.height }
]);
});
it('should layout straight between subProcess and task above (task -> subProcess)', function() {
// when
var connection = connect('Task_Top', 'SubProcess_Center'),
source = connection.source,
target = connection.target;
var expectedX = getMid(source).x;
// then
expect(connection).to.have.waypoints([
{ x: expectedX, y: source.y + source.height },
{ x: expectedX, y: target.y }
]);
});
});
describe('vertical subProcess', function() {
var diagramXML = require('./LayoutSequenceFlowSpec.vertical.subProcess.bpmn');
var testModules = [ coreModule, modelingModule ];
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
it('should layout straight between subProcesses (left -> right)', function() {
// when
var connection = connect('SubProcess_Center', 'SubProcess_Right'),
source = connection.source,
target = connection.target;
var expectedY = getMid(target).y;
// then
expect(connection).to.have.waypoints([
{ x: source.x + source.width, y: expectedY },
{ x: target.x, y: expectedY }
]);
});
it('should layout straight between subProcesses (right -> left)', function() {
// when
var connection = connect('SubProcess_Right', 'SubProcess_Center'),
source = connection.source,
target = connection.target;
var expectedY = getMid(target).y;
// then
expect(connection).to.have.waypoints([
{ x: source.x, y: expectedY },
{ x: target.x + target.width, y: expectedY }
]);
});
it('should layout straight between subProcess and task below (subProcess -> task)',
function() {
// when
var connection = connect('SubProcess_Center', 'Task_Bottom'),
source = connection.source,
target = connection.target;
var expectedX = getMid(target).x;
// then
expect(connection).to.have.waypoints([
{ x: expectedX, y: source.y + source.height },
{ x: expectedX, y: target.y }
]);
}
);
it('should layout straight between subProcess and task below (task -> subProcess)',
function() {
// when
var connection = connect('Task_Bottom', 'SubProcess_Center'),
source = connection.source,
target = connection.target;
var expectedX = getMid(source).x;
// then
expect(connection).to.have.waypoints([
{ x: expectedX, y: source.y },
{ x: expectedX, y: target.y + target.height }
]);
}
);
it('should layout straight between subProcess and task next to it (subProcess -> task)', function() {
// when
var connection = connect('SubProcess_Center', 'Task_Left'),
source = connection.source,
target = connection.target;
var expectedY = getMid(target).y;
// then
expect(connection).to.have.waypoints([
{ x: source.x, y: expectedY },
{ x: target.x + target.width, y: expectedY }
]);
});
it('should layout straight between subProcess and task next to it (task -> subProcess)', function() {
// when
var connection = connect('Task_Left', 'SubProcess_Center'),
source = connection.source,
target = connection.target;
var expectedY = getMid(source).y;
// then
expect(connection).to.have.waypoints([
{ x: source.x + source.width, y: expectedY },
{ x: target.x, y: expectedY }
]);
});
});
});
================================================
FILE: test/spec/features/modeling/layout/LayoutSequenceFlowSpec.subProcess.bpmn
================================================
================================================
FILE: test/spec/features/modeling/layout/LayoutSequenceFlowSpec.vertical.boundaryEvents.bpmn
================================================
================================================
FILE: test/spec/features/modeling/layout/LayoutSequenceFlowSpec.vertical.boundaryEventsLoops.bpmn
================================================
================================================
FILE: test/spec/features/modeling/layout/LayoutSequenceFlowSpec.vertical.flowElements.bpmn
================================================
SequenceFlow_1
SequenceFlow_2
SequenceFlow_3
SequenceFlow_4
SequenceFlow_1
SequenceFlow_2
SequenceFlow_3
SequenceFlow_4
================================================
FILE: test/spec/features/modeling/layout/LayoutSequenceFlowSpec.vertical.subProcess.bpmn
================================================
================================================
FILE: test/spec/features/modeling-feedback/ModelingFeedback.bpmn
================================================
================================================
FILE: test/spec/features/modeling-feedback/ModelingFeedbackSpec.js
================================================
import {
bootstrapModeler,
getBpmnJS,
inject
} from 'test/TestHelper';
import {
createCanvasEvent as canvasEvent
} from 'test/util/MockEvents';
import coreModule from 'lib/core';
import modelingModule from 'lib/features/modeling';
import modelingFeedbackModule from 'lib/features/modeling-feedback';
describe('features/modeling - ModelingFeedback', function() {
var collaborationDiagramXML = require('./ModelingFeedback.bpmn');
beforeEach(bootstrapModeler(collaborationDiagramXML, {
modules: [
coreModule,
modelingModule,
modelingFeedbackModule
]
}));
it('should indicate error when placing flow elements inside collaboration', inject(
function(create, canvas, elementFactory, dragging) {
// given
var task = elementFactory.createShape({ type: 'bpmn:Task' });
var collaboration = canvas.getRootElement();
var collaborationGfx = canvas.getGraphics(collaboration);
create.start(canvasEvent({ x: 100, y: 100 }), task);
dragging.hover({ element: collaboration, gfx: collaborationGfx });
// when
dragging.end();
// then
expectTooltip('error', 'flow elements must be children of pools/participants');
}));
it('should indicate error when placing data objects inside collaboration', inject(
function(create, canvas, elementFactory, dragging) {
// given
var dataObject = elementFactory.createShape({ type: 'bpmn:DataObjectReference' });
var collaboration = canvas.getRootElement();
var collaborationGfx = canvas.getGraphics(collaboration);
create.start(canvasEvent({ x: 150, y: 150 }), dataObject);
dragging.hover({ element: collaboration, gfx: collaborationGfx });
// when
dragging.end();
// then
expectTooltip('error', 'Data object must be placed within a pool/participant.');
}
));
});
function expectTooltip(cls, message) {
return getBpmnJS().invoke(function(canvas) {
var tooltipEl = document.querySelector('[data-tooltip-id]', canvas.getContainer());
expect(tooltipEl.textContent).to.eql(message);
expect(tooltipEl.classList.contains(cls));
});
}
================================================
FILE: test/spec/features/ordering/BpmnDiOrderingSpec.js
================================================
import {
bootstrapModeler,
getBpmnJS
} from 'test/TestHelper';
import {
add,
attach,
connect
} from './Helper';
import modelingModule from 'lib/features/modeling';
import coreModule from 'lib/core';
import emptyProcessXML from 'test/fixtures/bpmn/collaboration/process-empty.bpmn';
describe('features/modeling - di ordering', function() {
var testModules = [ coreModule, modelingModule ];
describe('boundary events', function() {
beforeEach(bootstrapModeler(emptyProcessXML, { modules: testModules }));
it('should place after tasks', function() {
// when
var task1 = add({ type: 'bpmn:Task' }, { x: 100, y: 100 }),
event = attach(
{ type: 'bpmn:BoundaryEvent' },
{ x: 100, y: 140 },
task1
),
task2 = add({ type: 'bpmn:Task' }, { x: 300, y: 100 });
// then
return expectDiOrder([ 'Process_1', task1.id, task2.id, event.id ]);
});
});
describe('collaboration', function() {
beforeEach(bootstrapModeler(emptyProcessXML, { modules: testModules }));
it('should place di elements in correct order', function() {
// given
var canvas = getBpmnJS().get('canvas'),
root;
// when
var participant1 = add(
{ type: 'bpmn:Participant', height: 300 }, { x: 300, y: 200 }, 'Process_1'
);
root = canvas.getRootElement();
var participant2 = add({ type: 'bpmn:Participant' }, { x: 300, y: 500 }),
task1 = add({ type: 'bpmn:Task' }, { x: 400, y: 150 }, participant1.id),
task2 = add({ type: 'bpmn:Task' }, { x: 250, y: 150 }, participant1.id);
var messageFlow1 = connect(task1, participant2),
messageFlow2 = connect(participant2, participant1),
sequenceFlow = connect(task2, task1);
// then
return expectDiOrder([
root.id,
participant1.id,
task1.id,
task2.id,
sequenceFlow.id,
participant2.id,
messageFlow1.id,
messageFlow2.id
]);
});
});
describe('subprocess', function() {
beforeEach(bootstrapModeler(emptyProcessXML, { modules: testModules }));
it('should place di elements in correct order', function() {
// given
var canvas = getBpmnJS().get('canvas'),
root;
// when
var subProcess = add(
{ type: 'bpmn:SubProcess', isExpanded: true, width: 300, height: 200 }, { x: 300, y: 200 }
);
var participant = add({ type: 'bpmn:Participant', width: 500, height: 300 }, { x: 300, y: 200 }),
task1 = add({ type: 'bpmn:Task' }, { x: 250, y: 200 }, subProcess.id);
root = canvas.getRootElement();
// then
return expectDiOrder([
root.id,
participant.id,
subProcess.id,
task1.id,
]);
});
it('should order subprocess planes', function() {
// given
var canvas = getBpmnJS().get('canvas'),
root;
// when
var subProcess = add(
{ type: 'bpmn:SubProcess', isExpanded: false, width: 300, height: 200 }, { x: 300, y: 200 }
);
var participant = add({ type: 'bpmn:Participant', width: 500, height: 300 }, { x: 300, y: 200 }),
task1 = add({ type: 'bpmn:Task' }, { x: 250, y: 200 }, subProcess.id + '_plane');
root = canvas.getRootElement();
// then
// subProcess id exists twice: once as collapsed shape and once as plane element
return expectDiOrder([
root.id,
participant.id,
subProcess.id,
subProcess.id,
task1.id,
]);
});
});
describe('wrong ordering in xml', function() {
var diagramXML = require('./wrong-di-order.bpmn');
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
it('should correctly order di elements on export', function() {
// then
return expectDiOrder(
[
'Page1Process',
'MakeBookingSubProcess',
'StartEvent_1',
'ExclusiveGateway_1l6x19l',
'BookFlightTask',
'CancelFlightTask',
'BookHotelTask',
'ParalelGateway',
'TravelBookedEndEvent',
'CancelHotelTask',
'HandleCompensationSubProcess',
'BookingStartEvent',
'ParallelGateway_0vh9j6n',
'FlightEvent',
'HotelEvent',
'ParallelGateway_1ycdyix',
'EndEvent_0nr3cro',
'SequenceFlow_0cip1mz',
'SequenceFlow_09qgqyw',
'SequenceFlow_0i33vwg',
'SequenceFlow_03663sw',
'SequenceFlow_0zpw5ma',
'SequenceFlow_0e6xitm',
'FlightBoundaryEvent',
'HotelBoundaryEvent',
'SequenceFlow_0e0tkzl',
'SequenceFlow_1244t37',
'SequenceFlow',
'SequenceFlow_1',
'SequenceFlow_2',
'SequenceFlow_3',
'Association_0qea76h',
'Association_1',
'CancelRequestEndEvent',
'RequestCancelledEndEvent',
'GatewayGateway',
'OfferApprovedEvent',
'N24HoursEvent',
'N24HoursEvent1',
'CancelRequestEvent',
'MakeFlyAndHotelOfferTask',
'RequestCreditCardInformationTask',
'NotifyCustomerOfferExpiredTask',
'UpdateCustomerRecordTask',
'ReceiveTravelRequestStartEvent',
'SequenceFlow_0zafwi9',
'SequenceFlow_0nqtgik',
'SequenceFlow_0qen1he',
'SequenceFlow_1ysz115',
'SequenceFlow_1sv6jqd',
'SequenceFlow_1kwx6kg',
'SequenceFlow_1fp5smb',
'SequenceFlow_1c22uay',
'SequenceFlow_13g7fzw'
]
);
});
});
});
// helpers ////////////
function expectDiOrder(expectedOrder) {
return getBpmnJS().saveXML({ format: true }).then(function(result) {
var xml = result.xml;
var pattern = /bpmnElement="([^"]+)"/g,
exportedOrder = [],
match = pattern.exec(xml);
while (match !== null) {
exportedOrder.push(match[1]);
match = pattern.exec(xml);
}
expect(exportedOrder).to.eql(expectedOrder);
});
}
================================================
FILE: test/spec/features/ordering/BpmnOrderingProviderSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import {
move,
attach,
connect,
expectZOrder
} from './Helper';
import modelingModule from 'lib/features/modeling';
import coreModule from 'lib/core';
describe('features/modeling - ordering', function() {
var testModules = [
coreModule,
modelingModule
];
describe('boundary events', function() {
describe('move', function() {
var diagramXML = require('./ordering.bpmn');
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
it('should stay in front of Task', inject(function() {
// when
move('Task_With_Boundary');
// then
expectZOrder('Task_With_Boundary', 'BoundaryEvent');
}));
it('should stay in front of Task, moving both', inject(function() {
// when
move([ 'BoundaryEvent', 'Task_With_Boundary' ], 'Participant_StartEvent');
// then
expectZOrder('Task_With_Boundary', 'BoundaryEvent');
}));
});
describe('add', function() {
var diagramXML = require('./ordering-start-event.bpmn');
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
it('should add in front of Task', inject(function() {
// when
var boundaryShape = attach({ type: 'bpmn:BoundaryEvent' }, { x: 300, y: 80 }, 'Task');
// then
expectZOrder('Task', boundaryShape.id);
}));
});
});
describe('participants', function() {
var diagramXML = require('./ordering.bpmn');
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
it('should stay behind MessageFlow', inject(function() {
// when
move('Participant', 'Collaboration');
// then
expectZOrder('Participant_StartEvent', 'Participant', 'MessageFlow');
}));
it('should stay behind Group', inject(function() {
// when
move('Participant', 'Collaboration');
// then
expectZOrder('Participant_StartEvent', 'Participant', 'Group');
}));
it('should stay behind DataInputAssociation when moving Participant with DataStore', inject(function() {
// when
move('Participant', { x: 5, y: 5 });
// then
expectZOrder('Participant', 'DataInputAssociation');
expectZOrder('Participant_StartEvent', 'DataInputAssociation');
}));
it('should stay behind DataInputAssociation when moving Participant with Task', inject(function() {
// when
move('Participant_StartEvent', { x: 5, y: 5 });
// then
expectZOrder('Participant', 'DataInputAssociation');
expectZOrder('Participant_StartEvent', 'DataInputAssociation');
}));
it('should stay behind DataOutputAssociation when moving Participant with DataStore', inject(function() {
// when
move('Participant', { x: 5, y: 5 });
// then
expectZOrder('Participant', 'DataOutputAssociation');
expectZOrder('Participant_StartEvent', 'DataOutputAssociation');
}));
it('should stay behind DataOutputAssociation when moving Participant with Task', inject(function() {
// when
move('Participant_StartEvent', { x: 5, y: 5 });
// then
expectZOrder('Participant', 'DataOutputAssociation');
expectZOrder('Participant_StartEvent', 'DataOutputAssociation');
}));
});
describe('sub processes', function() {
var diagramXML = require('./ordering-subprocesses.bpmn');
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
it('should stay behind boundary events', inject(function() {
// when
move('BoundaryEvent_SubProcess', { x: 50, y: 0 }, 'SubProcess_1', true);
// then
expectZOrder('SubProcess_1', 'BoundaryEvent_SubProcess');
}));
it('should stay behind tasks', inject(function() {
// when
move([ 'Task_1', 'Task_2' ], { x: 50, y: 0 }, 'SubProcess_1');
// then
expectZOrder('SubProcess_1', 'Task_1', 'Task_2');
}));
it('should be in front of tasks if task is not a child', inject(function() {
// when
move([ 'Task_1', 'Task_2' ], { x: 200, y: 0 }, 'Root');
// then
expectZOrder('Task_1', 'Task_2', 'SubProcess_1');
}));
});
describe('transaction', function() {
var diagramXML = require('./ordering-subprocesses.bpmn');
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
it('should stay behind boundary events', inject(function() {
// when
move('BoundaryEvent_Transaction', { x: 50, y: 0 }, 'Transaction_1', true);
// then
expectZOrder('Transaction_1', 'BoundaryEvent_Transaction');
}));
it('should stay behind tasks', inject(function() {
// when
move([ 'Task_1', 'Task_2' ], { x: 50, y: 0 }, 'Transaction_1');
// then
expectZOrder('Transaction_1', 'Task_1', 'Task_2');
}));
it('should be in front of tasks if task is not a child', inject(function() {
// when
move([ 'Task_1', 'Task_2' ], { x: 200, y: 0 }, 'Root');
// then
expectZOrder('Task_1', 'Task_2', 'Transaction_1');
}));
});
describe('labels', function() {
var diagramXML = require('./ordering.bpmn');
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
describe('should stay always in front', function() {
it('moving onto ', inject(function() {
// when
move('SequenceFlow_label', { x: 300, y: 0 }, 'Collaboration', false);
// then
expectZOrder('Collaboration', 'Participant', 'SequenceFlow_label');
}));
it('moving onto ', inject(function() {
// when
move('StartEvent_label', { x: 50, y: -330 }, 'Participant', false);
// then
expectZOrder(
'Participant',
'Task_With_Boundary',
'BoundaryEvent',
'Participant_StartEvent',
'StartEvent_label'
);
}));
it('move with label onto ', inject(function() {
// when
move('StartEvent', { x: 0, y: -330 }, 'Participant', false);
// then
expectZOrder(
'Participant',
'Participant_StartEvent',
'StartEvent_label'
);
}));
it('move with label onto ', inject(function() {
// when
move('DataStore', { x: -150, y: 330 }, 'Participant_StartEvent', false);
// then
expectZOrder(
'Participant',
'Participant_StartEvent',
'DataStore',
'DataStore_label'
);
}));
});
});
describe('groups', function() {
var diagramXML = require('./groups.bpmn');
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
describe('should stay always in front', function() {
it('moving onto ', inject(function() {
// when
move('Group', { x: 100, y: 0 }, 'StartEvent', false);
// then
expectZOrder('StartEvent', 'Group');
}));
it('moving onto ', inject(function() {
// when
move('Group', { x: 200, y: 50 }, 'Task', false);
// then
expectZOrder('Task', 'Group');
}));
it('move onto ', inject(function() {
// when
move('Group', { x: 400, y: 0 }, 'SubProcess', false);
// then
expectZOrder('SubProcess', 'Group');
}));
it('move onto ', inject(function() {
// when
move('Group', { x: 50, y: 0 }, 'Participant', false);
// then
expectZOrder('Participant', 'Group');
}));
});
describe('inside subprocess', function() {
var diagramXML = require('./collapsed-subprocess.bpmn');
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
describe('should stay always in front', function() {
it('moving onto ', inject(function() {
// when
move('Group', { x: 100, y: 0 }, 'StartEvent', false);
// then
expectZOrder('StartEvent', 'Group');
}));
it('moving onto ', inject(function() {
// when
move('Group', { x: 200, y: 50 }, 'Task', false);
// then
expectZOrder('Task', 'Group');
}));
it('move onto ', inject(function() {
// when
move('Group', { x: 400, y: 0 }, 'SubProcess', false);
// then
expectZOrder('SubProcess', 'Group');
}));
});
});
});
describe('sequence flows', function() {
var diagramXML = require('./ordering.bpmn');
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
it('should render sequence flows behind tasks', inject(function() {
// assume
expectZOrder('Task', 'BoundaryEvent', 'SequenceFlow');
// when
var connection = connect('BoundaryEvent', 'Task');
// then
expectZOrder('Task', 'BoundaryEvent', connection, connection.label);
}));
});
describe('data associations', function() {
var diagramXML = require('./data-association.bpmn');
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
it('should render data associations infront of Collaboration', inject(function() {
// when
var connection = connect('DataStore', 'Task_1');
// then
expectZOrder('Collaboration_1', 'DataStore', connection);
}));
describe('inside subprocesses', function() {
it('should render data associations behind other Subprocess', inject(function() {
// assumne
expectZOrder('SubProcess_1', 'SubProcess_2');
// when
var connection = connect('DataReference_1', 'Task_1');
// then
expectZOrder('SubProcess_1', connection, 'SubProcess_2');
}));
it('should render in collapsed subprocess plane', inject(function() {
// when
var connection = connect('DataReference_2', 'Task_2');
// then
expectZOrder('collapsedSubProcess_plane', 'DataReference_2', connection);
}));
});
});
});
================================================
FILE: test/spec/features/ordering/Helper.js
================================================
import {
getBpmnJS
} from 'test/TestHelper';
import {
map,
forEach
} from 'min-dash';
function sign(x) {
x = +x; // convert to a number
if (x === 0 || isNaN(x)) {
return x;
}
return x > 0 ? 1 : -1;
}
export function move(elementIds, delta, targetId, isAttach) {
if (typeof elementIds === 'string') {
elementIds = [ elementIds ];
}
if (typeof delta !== 'object') {
isAttach = targetId;
targetId = delta;
delta = { x: 0, y: 0 };
}
if (typeof targetId !== 'string') {
isAttach = targetId;
targetId = null;
}
return getBpmnJS().invoke(function(canvas, elementRegistry, modeling) {
function getElement(id) {
var element = elementRegistry.get(id);
expect(element).to.exist;
return element;
}
var elements = map(elementIds, getElement),
target;
if (targetId === 'Root') {
target = canvas.getRootElement();
} else {
target = targetId && getElement(targetId);
}
var hints = isAttach ? { attach: true } : {};
return modeling.moveElements(elements, delta, target, hints);
});
}
export function add(attrs, position, target, isAttach) {
return getBpmnJS().invoke(function(canvas, elementRegistry, modeling) {
function getElement(id) {
var element = elementRegistry.get(id);
expect(element).to.exist;
return element;
}
if (!target) {
target = canvas.getRootElement();
} else if (typeof target === 'string') {
target = getElement(target);
}
return modeling.createShape(attrs, position, target, { attach: isAttach });
});
}
export function connect(source, target) {
return getBpmnJS().invoke(function(canvas, elementRegistry, modeling) {
function getElement(id) {
var element = elementRegistry.get(id);
expect(element).to.exist;
return element;
}
if (typeof target === 'string') {
target = getElement(target);
}
if (typeof source === 'string') {
source = getElement(source);
}
return modeling.connect(source, target);
});
}
export function attach(attrs, position, target) {
return add(attrs, position, target, true);
}
function getAncestors(element) {
var ancestors = [];
while (element) {
ancestors.push(element);
element = element.parent;
}
return ancestors;
}
function compareZOrder(a, b) {
var elementA,
elementB;
getBpmnJS().invoke(function(elementRegistry) {
function getElement(id) {
var element = elementRegistry.get(id);
expect(element, 'element <' + id + '>').to.exist;
return element;
}
if (typeof a === 'string') {
a = getElement(a);
}
if (typeof b === 'string') {
b = getElement(b);
}
elementA = a;
elementB = b;
});
var aAncestors = getAncestors(elementA),
bAncestors = getAncestors(elementB);
var sharedRoot = aAncestors.reduce(function(result, aAncestor, aParentIndex) {
if (result) {
return result;
}
var bParentIndex = bAncestors.indexOf(aAncestor);
if (bParentIndex !== -1) {
return {
a: aAncestors[aParentIndex - 1],
b: bAncestors[bParentIndex - 1],
parent: aAncestor
};
}
}, false);
// b contained in a
if (!sharedRoot.a) {
return -1;
}
// a contained in b
if (!sharedRoot.b) {
return 1;
}
var aIndex = sharedRoot.parent.children.indexOf(sharedRoot.a),
bIndex = sharedRoot.parent.children.indexOf(sharedRoot.b);
return sign(aIndex - bIndex);
}
export function expectZOrder() {
var elements = Array.prototype.slice.call(arguments);
var next;
forEach(elements, function(e, idx) {
next = elements[idx + 1];
if (next && compareZOrder(e, next) !== -1) {
throw new Error(
`expected to be in front of `
);
}
});
return true;
}
================================================
FILE: test/spec/features/ordering/collapsed-subprocess.bpmn
================================================
================================================
FILE: test/spec/features/ordering/data-association.bpmn
================================================
================================================
FILE: test/spec/features/ordering/groups.bpmn
================================================
================================================
FILE: test/spec/features/ordering/ordering-start-event.bpmn
================================================
================================================
FILE: test/spec/features/ordering/ordering-subprocesses.bpmn
================================================
================================================
FILE: test/spec/features/ordering/ordering.bpmn
================================================
SequenceFlow
SequenceFlow
DataStore
Property_17cs768
DataStore_With_Output
================================================
FILE: test/spec/features/ordering/wrong-di-order.bpmn
================================================
SequenceFlow_13g7fzw
SequenceFlow_0e0tkzl
SequenceFlow_0e0tkzl
SequenceFlow_1244t37
SequenceFlow
SequenceFlow_1244t37
SequenceFlow_1
SequenceFlow
SequenceFlow_2
SequenceFlow_1
SequenceFlow_2
SequenceFlow_3
SequenceFlow_3
SequenceFlow_0e6xitm
SequenceFlow_0e6xitm
SequenceFlow_03663sw
SequenceFlow_0zpw5ma
SequenceFlow_0zpw5ma
SequenceFlow_0i33vwg
SequenceFlow_03663sw
SequenceFlow_09qgqyw
SequenceFlow_09qgqyw
SequenceFlow_0i33vwg
SequenceFlow_0cip1mz
SequenceFlow_0cip1mz
SequenceFlow_0zafwi9
SequenceFlow_0nqtgik
SequenceFlow_0qen1he
SequenceFlow_1ysz115
SequenceFlow_1sv6jqd
SequenceFlow_0qen1he
SequenceFlow_1kwx6kg
SequenceFlow_1ysz115
SequenceFlow_1sv6jqd
SequenceFlow_1fp5smb
SequenceFlow_1c22uay
SequenceFlow_0nqtgik
SequenceFlow_1kwx6kg
SequenceFlow_13g7fzw
SequenceFlow_1fp5smb
SequenceFlow_0zafwi9
SequenceFlow_1c22uay
================================================
FILE: test/spec/features/outline/OutlineProvider.bpmn
================================================
================================================
FILE: test/spec/features/outline/OutlineProviderSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import coreModule from 'lib/core';
import modelingModule from 'lib/features/modeling';
import outlineProviderModule from 'lib/features/outline';
import diagramXml from './OutlineProvider.bpmn';
import {
DATA_OBJECT_REFERENCE_OUTLINE_PATH,
DATA_STORE_REFERENCE_OUTLINE_PATH
} from 'lib/features/outline/OutlineUtil';
import {
query as domQuery
} from 'min-dom';
describe('features/outline - outline provider', function() {
var testModules = [
coreModule,
modelingModule,
outlineProviderModule
];
beforeEach(bootstrapModeler(diagramXml, { modules: testModules }));
describe('should provide outline for', function() {
it('event', inject(function(elementRegistry, outline) {
// given
var event = elementRegistry.get('Event');
// when
var outlineShape = outline.getOutline(event);
// then
expect(outlineShape).to.exist;
expect(outlineShape.tagName).to.eql('circle');
}));
it('task', inject(function(elementRegistry, outline) {
// given
var task = elementRegistry.get('Task');
// when
var outlineShape = outline.getOutline(task);
// then
expect(outlineShape).to.exist;
expect(outlineShape.tagName).to.eql('rect');
}));
it('call activity', inject(function(elementRegistry, outline) {
// given
var callActivity = elementRegistry.get('CallActivity');
// when
var outlineShape = outline.getOutline(callActivity);
// then
expect(outlineShape).to.exist;
expect(outlineShape.tagName).to.eql('rect');
}));
it('gateway', inject(function(elementRegistry, outline) {
// given
var gateway = elementRegistry.get('Gateway');
// when
var outlineShape = outline.getOutline(gateway);
// then
expect(outlineShape).to.exist;
expect(outlineShape.tagName).to.eql('rect');
expect(outlineShape.style.transform).to.eql('rotate(45deg)');
}));
it('sub process', inject(function(elementRegistry, outline) {
// given
var subProcess = elementRegistry.get('SubProcess');
// when
var outlineShape = outline.getOutline(subProcess);
// then
expect(outlineShape).to.exist;
expect(outlineShape.tagName).to.eql('rect');
}));
it('data object', inject(function(elementRegistry, outline) {
// given
var dataObject = elementRegistry.get('DataObject');
// when
var outlineShape = outline.getOutline(dataObject);
// then
expect(outlineShape).to.exist;
expect(outlineShape.tagName).to.eql('path');
expect(outlineShape.getAttribute('d')).to.eql(DATA_OBJECT_REFERENCE_OUTLINE_PATH);
}));
it('data store', inject(function(elementRegistry, outline) {
// given
var dataStore = elementRegistry.get('DataStore');
// when
var outlineShape = outline.getOutline(dataStore);
// then
expect(outlineShape).to.exist;
expect(outlineShape.tagName).to.eql('path');
expect(outlineShape.getAttribute('d')).to.eql(DATA_STORE_REFERENCE_OUTLINE_PATH);
}));
});
describe('update', function() {
describe('should update label', function() {
var DELTA = 3;
it('should update label according to label dimentions', inject(function(elementRegistry, selection, modeling) {
// given
var event = elementRegistry.get('Event');
var externalLabel = event.label;
selection.select(externalLabel);
var outlineShape = domQuery('.selected .djs-outline', outlineShape);
// then
let bounds = outlineShape.getBoundingClientRect();
expect(bounds.width).to.be.closeTo(34, DELTA);
expect(bounds.height).to.be.closeTo(24, DELTA);
// when
modeling.updateLabel(externalLabel, 'fooooooooooooooo');
// then
bounds = outlineShape.getBoundingClientRect();
expect(bounds.width).to.be.closeTo(93, DELTA);
expect(bounds.height).to.be.closeTo(37, DELTA);
}));
});
describe('should update dimensions on resize', function() {
it('sub process', inject(function(elementRegistry, outline, modeling) {
// given
var subProcess = elementRegistry.get('SubProcess');
var outlineShape = outline.getOutline(subProcess);
// when
modeling.resizeShape(subProcess, { x: 339, y: 142, width: 250, height: 250 });
outline.updateShapeOutline(outlineShape, subProcess);
// then
expect(outlineShape.getAttribute('width')).to.eql('260');
expect(outlineShape.getAttribute('height')).to.eql('260');
}));
it('group', inject(function(elementRegistry, outline, modeling) {
// given
var group = elementRegistry.get('Group');
var outlineShape = outline.getOutline(group);
// when
modeling.resizeShape(group, { x: 339, y: 142, width: 250, height: 250 });
outline.updateShapeOutline(outlineShape, group);
// then
expect(outlineShape.getAttribute('width')).to.eql('260');
expect(outlineShape.getAttribute('height')).to.eql('260');
}));
});
});
});
================================================
FILE: test/spec/features/palette/PaletteProviderSpec.js
================================================
import {
bootstrapModeler,
getBpmnJS,
inject
} from 'test/TestHelper';
import coreModule from 'lib/core';
import createModule from 'diagram-js/lib/features/create';
import modelingModule from 'lib/features/modeling';
import paletteModule from 'lib/features/palette';
import { createMoveEvent } from 'diagram-js/lib/features/mouse/Mouse';
import { is } from 'lib/util/ModelUtil';
import {
createCanvasEvent as canvasEvent
} from '../../../util/MockEvents';
import {
query as domQuery,
queryAll as domQueryAll
} from 'min-dom';
describe('features/palette', function() {
var diagramXML = require('../../../fixtures/bpmn/features/replace/01_replace.bpmn');
var testModules = [
coreModule,
createModule,
modelingModule,
paletteModule
];
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
it('should provide BPMN modeling palette', inject(function(canvas) {
// when
var paletteElement = domQuery('.djs-palette', canvas._container);
var entries = domQueryAll('.entry', paletteElement);
// then
expect(entries.length).to.equal(14);
}));
describe('sub process', function() {
it('should create sub process with start event', inject(function(dragging) {
// when
triggerPaletteEntry('create.subprocess-expanded');
// then
var context = dragging.context(),
elements = context.data.elements;
expect(elements).to.have.length(2);
expect(is(elements[0], 'bpmn:SubProcess')).to.be.true;
expect(is(elements[1], 'bpmn:StartEvent')).to.be.true;
expect(elements[0].di.isExpanded).to.be.true;
}));
it('should select sub-process', inject(function(canvas, dragging, selection) {
// given
var rootElement = canvas.getRootElement(),
rootGfx = canvas.getGraphics(rootElement);
triggerPaletteEntry('create.subprocess-expanded');
// when
dragging.hover({ element: rootElement, gfx: rootGfx });
dragging.move(canvasEvent({ x: 100, y: 100 }));
// when
dragging.end();
// then
var selected = selection.get();
expect(selected).to.have.length(1);
expect(is(selected[0], 'bpmn:SubProcess')).to.be.true;
}));
});
describe('gateway', function() {
it('should set gateway marker', inject(function(dragging) {
// when
triggerPaletteEntry('create.exclusive-gateway');
// then
var context = dragging.context(),
elements = context.data.elements;
expect(elements).to.have.length(1);
expect(is(elements[0], 'bpmn:ExclusiveGateway')).to.be.true;
expect(elements[0].di.isMarkerVisible).to.be.true;
}));
});
describe('tools', function() {
it('should not fire on globalConnect', inject(
function(eventBus) {
// given
var moveSpy = sinon.spy();
eventBus.on('global-connect.move', moveSpy);
// when
triggerPaletteEntry('global-connect-tool');
// then
expect(moveSpy).to.not.have.been.called;
}
));
});
});
// helpers //////////
function triggerPaletteEntry(id) {
getBpmnJS().invoke(function(palette) {
var entry = palette.getEntries()[ id ];
if (entry && entry.action && entry.action.click) {
entry.action.click(createMoveEvent(0, 0));
}
});
}
================================================
FILE: test/spec/features/popup-menu/ReplaceMenuProvider.collapsedSubProcess.bpmn
================================================
SequenceFlow_3
SequenceFlow_1
SequenceFlow_1
SequenceFlow_2
SequenceFlow_2
SequenceFlow_3
SequenceFlow_4
SequenceFlow_4
SequenceFlow_5
SequenceFlow_5
================================================
FILE: test/spec/features/popup-menu/ReplaceMenuProvider.compensation-activity.bpmn
================================================
================================================
FILE: test/spec/features/popup-menu/ReplaceMenuProvider.conditionalFlows.bpmn
================================================
SequenceFlow_1
SequenceFlow_1
SequenceFlow_2
SequenceFlow_3
SequenceFlow_2
SequenceFlow_3
================================================
FILE: test/spec/features/popup-menu/ReplaceMenuProvider.defaultFlows.bpmn
================================================
SequenceFlow_1
SequenceFlow_2
SequenceFlow_3
SequenceFlow_1
SequenceFlow_2
SequenceFlow_3
SequenceFlow_4
SequenceFlow_4
SequenceFlow_15f5knn
SequenceFlow_10yqnek
SequenceFlow_15f5knn
SequenceFlow_10yqnek
================================================
FILE: test/spec/features/popup-menu/ReplaceMenuProvider.defaultFlowsFromComplexGateways.bpmn
================================================
SequenceFlow_1
SequenceFlow_1
SequenceFlow_2
SequenceFlow_2
================================================
FILE: test/spec/features/popup-menu/ReplaceMenuProvider.defaultFlowsFromInclusiveGateways.bpmn
================================================
SequenceFlow_1
SequenceFlow_1
SequenceFlow_2
SequenceFlow_2
================================================
FILE: test/spec/features/popup-menu/ReplaceMenuProvider.events.bpmn
================================================
================================================
FILE: test/spec/features/popup-menu/ReplaceMenuProvider.pools.bpmn
================================================
Flow_1
Flow_1
Flow_2
Flow_2
================================================
FILE: test/spec/features/popup-menu/ReplaceMenuProvider.subProcesses.bpmn
================================================
================================================
FILE: test/spec/features/popup-menu/ReplaceMenuProviderSpec.js
================================================
import { expectToBeAccessible } from '@bpmn-io/a11y';
import {
bootstrapModeler,
getBpmnJS,
inject
} from 'test/TestHelper';
import {
createEvent as globalEvent
} from '../../../util/MockEvents';
import autoResizeModule from 'lib/features/auto-resize';
import coreModule from 'lib/core';
import customRulesModule from '../../../util/custom-rules';
import modelingModule from 'lib/features/modeling';
import replaceMenuProviderModule from 'lib/features/popup-menu';
import camundaModdleModule from 'camunda-bpmn-moddle/lib';
import camundaPackage from 'camunda-bpmn-moddle/resources/camunda.json';
import {
query as domQuery,
queryAll as domQueryAll,
classes as domClasses
} from 'min-dom';
import { is } from 'lib/util/ModelUtil';
import { isExpanded } from 'lib/util/DiUtil';
import { getBusinessObject } from '../../../../lib/util/ModelUtil';
import { omit } from 'min-dash';
describe('features/popup-menu - replace menu provider', function() {
var diagramXMLMarkers = require('../../../fixtures/bpmn/draw/activity-markers-simple.bpmn'),
diagramXMLReplace = require('../../../fixtures/bpmn/features/replace/01_replace.bpmn'),
diagramXMLDataElements = require('../../../fixtures/bpmn/features/replace/data-elements.bpmn'),
diagramXMLDataStoresPositionedAgainstParticipant = require('../../../fixtures/bpmn/features/replace/data-stores-positioned-against-participant.bpmn'),
diagramXMLParticipants = require('../../../fixtures/bpmn/features/replace/participants.bpmn');
var testModules = [
coreModule,
modelingModule,
replaceMenuProviderModule,
customRulesModule
];
describe('data object - collection marker', function() {
beforeEach(bootstrapModeler(diagramXMLDataElements, { modules: testModules }));
it('should toggle on', inject(function(elementRegistry) {
// given
var dataObjectReference = elementRegistry.get('DataObjectReference_1');
openPopup(dataObjectReference);
// when
triggerAction('toggle-is-collection');
openPopup(dataObjectReference);
var isCollectionMarker = queryEntry('toggle-is-collection');
// then
expect(domClasses(isCollectionMarker).has('active')).to.be.true;
expect(dataObjectReference.businessObject.dataObjectRef.isCollection).to.be.true;
}));
it('should undo', inject(function(commandStack, elementRegistry) {
// given
var dataObjectReference = elementRegistry.get('DataObjectReference_1');
openPopup(dataObjectReference);
triggerAction('toggle-is-collection');
// when
commandStack.undo();
openPopup(dataObjectReference);
var isCollectionMarker = queryEntry('toggle-is-collection');
// then
expect(domClasses(isCollectionMarker).has('active')).to.be.false;
expect(dataObjectReference.businessObject.dataObjectRef.isCollection).not.to.be.true;
}));
it('should redo', inject(function(commandStack, elementRegistry) {
// given
var dataObjectReference = elementRegistry.get('DataObjectReference_1');
openPopup(dataObjectReference);
triggerAction('toggle-is-collection');
commandStack.undo();
// when
commandStack.redo();
openPopup(dataObjectReference);
var isCollectionMarker = queryEntry('toggle-is-collection');
// then
expect(domClasses(isCollectionMarker).has('active')).to.be.true;
expect(dataObjectReference.businessObject.dataObjectRef.isCollection).to.be.true;
}));
it('should toggle off', inject(function(elementRegistry) {
// given
var dataObjectReference = elementRegistry.get('DataObjectReference_1');
openPopup(dataObjectReference);
triggerAction('toggle-is-collection');
openPopup(dataObjectReference);
// when
triggerAction('toggle-is-collection');
openPopup(dataObjectReference);
var isCollectionMarker = queryEntry('toggle-is-collection');
// then
expect(domClasses(isCollectionMarker).has('active')).to.be.false;
expect(dataObjectReference.businessObject.dataObjectRef.isCollection).to.be.false;
}));
it('should activate marker of linked data object reference', inject(function(elementRegistry) {
// given
var dataObjectReference1 = elementRegistry.get('DataObjectReference_1');
var dataObjectReference2 = elementRegistry.get('DataObjectReference_2');
openPopup(dataObjectReference1);
// when
triggerAction('toggle-is-collection');
openPopup(dataObjectReference2);
var isCollectionMarker = queryEntry('toggle-is-collection');
// then
expect(domClasses(isCollectionMarker).has('active')).to.be.true;
}));
it('should deactivate marker of linked data object reference', inject(function(elementRegistry) {
// given
var dataObjectReference1 = elementRegistry.get('DataObjectReference_1');
var dataObjectReference2 = elementRegistry.get('DataObjectReference_2');
openPopup(dataObjectReference1);
triggerAction('toggle-is-collection');
openPopup(dataObjectReference1);
// when
triggerAction('toggle-is-collection');
openPopup(dataObjectReference2);
var isCollectionMarker = queryEntry('toggle-is-collection');
// then
expect(domClasses(isCollectionMarker).has('active')).to.be.false;
}));
});
describe('participants - multiplicity marker', function() {
beforeEach(bootstrapModeler(diagramXMLParticipants, { modules: testModules }));
it('should toggle on', inject(function(elementRegistry) {
// given
var participant = elementRegistry.get('Participant_1');
openPopup(participant);
// when
triggerAction('toggle-participant-multiplicity');
openPopup(participant);
var multiplicityMarker = queryEntry('toggle-participant-multiplicity');
// then
expect(domClasses(multiplicityMarker).has('active')).to.be.true;
expect(participant.businessObject.participantMultiplicity).to.exist;
}));
it('should undo', inject(function(commandStack, elementRegistry) {
// given
var participant = elementRegistry.get('Participant_1');
openPopup(participant);
triggerAction('toggle-participant-multiplicity');
// when
commandStack.undo();
openPopup(participant);
var multiplicityMarker = queryEntry('toggle-participant-multiplicity');
// then
expect(domClasses(multiplicityMarker).has('active')).to.be.false;
expect(participant.businessObject.participantMultiplicity).not.to.exist;
}));
it('should redo', inject(function(commandStack, elementRegistry) {
// given
var participant = elementRegistry.get('Participant_1');
openPopup(participant);
triggerAction('toggle-participant-multiplicity');
commandStack.undo();
// when
commandStack.redo();
openPopup(participant);
var multiplicityMarker = queryEntry('toggle-participant-multiplicity');
// then
expect(domClasses(multiplicityMarker).has('active')).to.be.true;
expect(participant.businessObject.participantMultiplicity).to.exist;
}));
it('should toggle off', inject(function(elementRegistry) {
// given
var participant = elementRegistry.get('Participant_1');
openPopup(participant);
triggerAction('toggle-participant-multiplicity');
openPopup(participant);
// when
triggerAction('toggle-participant-multiplicity');
openPopup(participant);
var multiplicityMarker = queryEntry('toggle-participant-multiplicity');
// then
expect(domClasses(multiplicityMarker).has('active')).to.be.false;
expect(participant.businessObject.participantMultiplicity).not.to.exist;
}));
});
describe('toggle', function() {
beforeEach(bootstrapModeler(diagramXMLMarkers,{
modules: Object.assign(testModules, camundaModdleModule),
moddleExtensions: {
camunda: camundaPackage
}
}));
var toggleActive;
beforeEach(inject(function(popupMenu) {
toggleActive = function(entryCls) {
return popupMenu._getEntry(entryCls).active;
};
}));
describe('active attribute', function() {
it('should be true for parallel marker', inject(function(elementRegistry) {
// given
var task = elementRegistry.get('ParallelTask'),
loopCharacteristics = task.businessObject.loopCharacteristics;
// assume
expect(loopCharacteristics.isSequential).to.be.false;
expect(loopCharacteristics.isSequential).to.exist;
// when
openPopup(task);
// then
expect(is(loopCharacteristics, 'bpmn:MultiInstanceLoopCharacteristics')).to.be.true;
expect(toggleActive('toggle-parallel-mi')).to.be.true;
}));
it('should be true for sequential marker', inject(function(elementRegistry) {
// given
var task = elementRegistry.get('SequentialTask'),
loopCharacteristics = task.businessObject.loopCharacteristics;
// assume
expect(loopCharacteristics.isSequential).to.be.true;
expect(is(loopCharacteristics, 'bpmn:MultiInstanceLoopCharacteristics')).to.be.true;
// when
openPopup(task);
// then
expect(toggleActive('toggle-sequential-mi')).to.be.true;
}));
it('should be true for loop marker', inject(function(elementRegistry) {
// given
var task = elementRegistry.get('LoopTask'),
loopCharacteristics = task.businessObject.loopCharacteristics;
// assume
expect(loopCharacteristics.isSequential).not.to.exist;
expect(is(loopCharacteristics, 'bpmn:MultiInstanceLoopCharacteristics')).to.be.false;
// when
openPopup(task);
// then
expect(toggleActive('toggle-loop')).to.be.true;
}));
});
describe('parallel toggle button', function() {
it('should toggle parallel marker off',
inject(function(elementRegistry) {
// given
var task = elementRegistry.get('ParallelTask');
openPopup(task);
// when
triggerAction('toggle-parallel-mi');
openPopup(task);
var parallelEntry = queryEntry('toggle-parallel-mi');
// then
expect(task.businessObject.loopCharacteristics).not.to.exist;
expect(domClasses(parallelEntry).has('active')).to.be.false;
})
);
it('should toggle parallel marker on', inject(function(elementRegistry) {
// given
var task = elementRegistry.get('Task');
openPopup(task);
// when
triggerAction('toggle-parallel-mi');
openPopup(task);
var parallelEntry = queryEntry('toggle-parallel-mi');
// then
expect(domClasses(parallelEntry).has('active')).to.be.true;
expect(task.businessObject.loopCharacteristics.isSequential).to.be.false;
expect(is(task.businessObject.loopCharacteristics, 'bpmn:MultiInstanceLoopCharacteristics')).to.be.true;
}));
it('should set sequential button inactive', inject(function(elementRegistry) {
// given
var task = elementRegistry.get('SequentialTask');
openPopup(task);
// when
triggerAction('toggle-parallel-mi');
openPopup(task);
var sequentialEntry = queryEntry('toggle-sequential-mi');
// then
expect(domClasses(sequentialEntry).has('active')).to.be.false;
}));
it('should set loop button inactive', inject(function(elementRegistry) {
// given
var task = elementRegistry.get('LoopTask');
openPopup(task);
// when
triggerAction('toggle-parallel-mi');
openPopup(task);
var loopEntry = queryEntry('toggle-loop');
// then
expect(domClasses(loopEntry).has('active')).to.be.false;
}));
it('should set loop characteristics type', inject(function(elementRegistry) {
// given
var task = elementRegistry.get('LoopTask'),
businessObject = getBusinessObject(task);
openPopup(task);
// when
triggerAction('toggle-parallel-mi');
// then
var newLoopCharacteristics = businessObject.loopCharacteristics;
expect(is(newLoopCharacteristics, 'bpmn:MultiInstanceLoopCharacteristics')).to.be.true;
expect(newLoopCharacteristics.isSequential).to.be.false;
}));
it('should keep sequential properties', inject(function(elementRegistry) {
// given
var task = elementRegistry.get('SequentialTask'),
businessObject = getBusinessObject(task),
loopCharacteristics = businessObject.get('loopCharacteristics');
openPopup(task);
// assume
expect(loopCharacteristics.get('isSequential')).to.be.true;
// when
triggerAction('toggle-parallel-mi');
// then
const newLoopCharacteristics = businessObject.get('loopCharacteristics');
expect(newLoopCharacteristics).to.equal(loopCharacteristics);
expect(newLoopCharacteristics.get('isSequential')).to.be.false;
expect(omit(newLoopCharacteristics, 'isSequential')).to.eql(omit(loopCharacteristics, 'isSequential'));
}));
});
describe('sequential toggle button', function() {
it('should toggle sequential marker off', inject(function(elementRegistry) {
// given
var task = elementRegistry.get('SequentialTask');
openPopup(task);
// when
triggerAction('toggle-sequential-mi');
openPopup(task);
var sequentialEntry = queryEntry('toggle-sequential-mi');
// then
expect(task.businessObject.loopCharacteristics).not.to.exist;
expect(domClasses(sequentialEntry).has('active')).to.be.false;
}));
it('should toggle sequential marker on', inject(function(elementRegistry) {
// given
var task = elementRegistry.get('Task');
openPopup(task);
// when
triggerAction('toggle-sequential-mi');
openPopup(task);
var sequentialEntry = queryEntry('toggle-sequential-mi');
// then
expect(domClasses(sequentialEntry).has('active')).to.be.true;
expect(task.businessObject.loopCharacteristics.isSequential).to.be.true;
expect(is(task.businessObject.loopCharacteristics, 'bpmn:MultiInstanceLoopCharacteristics')).to.be.true;
}));
it('should set loop button inactive', inject(function(elementRegistry) {
// given
var task = elementRegistry.get('LoopTask');
openPopup(task);
// when
triggerAction('toggle-sequential-mi');
openPopup(task);
var loopEntry = queryEntry('toggle-loop');
// then
expect(domClasses(loopEntry).has('active')).to.be.false;
}));
it('should set parallel button inactive', inject(function(elementRegistry) {
// given
var task = elementRegistry.get('ParallelTask');
openPopup(task);
// when
triggerAction('toggle-sequential-mi');
openPopup(task);
var parallelEntry = queryEntry('toggle-parallel-mi');
// then
expect(domClasses(parallelEntry).has('active')).to.be.false;
}));
it('should set loop characteristics type', inject(function(elementRegistry) {
// given
var task = elementRegistry.get('LoopTask'),
businessObject = getBusinessObject(task);
openPopup(task);
// when
triggerAction('toggle-sequential-mi');
// then
var newLoopCharacteristics = businessObject.loopCharacteristics;
expect(is(newLoopCharacteristics, 'bpmn:MultiInstanceLoopCharacteristics')).to.be.true;
expect(newLoopCharacteristics.isSequential).to.be.true;
}));
it('should keep parallel properties', inject(function(elementRegistry) {
// given
var task = elementRegistry.get('ParallelTask'),
businessObject = getBusinessObject(task),
loopCharacteristics = businessObject.get('loopCharacteristics');
openPopup(task);
// assume
expect(loopCharacteristics.get('isSequential')).to.be.false;
// when
triggerAction('toggle-sequential-mi');
// then
var newLoopCharacteristics = businessObject.get('loopCharacteristics');
expect(newLoopCharacteristics).to.equal(loopCharacteristics);
expect(newLoopCharacteristics.get('isSequential')).to.be.true;
expect(omit(newLoopCharacteristics, 'isSequential')).to.eql(omit(loopCharacteristics, 'isSequential'));
}));
});
describe('loop toggle button', function() {
it('should toggle loop marker off', inject(function(elementRegistry) {
// given
var task = elementRegistry.get('LoopTask');
openPopup(task);
// when
triggerAction('toggle-loop');
openPopup(task);
var loopEntry = queryEntry('toggle-loop');
// then
expect(domClasses(loopEntry).has('active')).to.be.false;
expect(is(task.businessObject.loopCharacteristics, 'bpmn:MultiInstanceLoopCharacteristics')).not.to.exist;
}));
it('should toggle loop marker on', inject(function(elementRegistry) {
// given
var task = elementRegistry.get('Task');
openPopup(task);
// when
triggerAction('toggle-loop');
openPopup(task);
var loopEntry = queryEntry('toggle-loop');
// then
expect(domClasses(loopEntry).has('active')).to.be.true;
expect(is(task.businessObject.loopCharacteristics, 'bpmn:StandardLoopCharacteristics')).to.be.true;
}));
it('should set sequential button inactive', inject(function(elementRegistry) {
// given
var task = elementRegistry.get('SequentialTask');
openPopup(task);
triggerAction('toggle-loop');
// when
openPopup(task);
var sequentialEntry = queryEntry('toggle-sequential-mi');
// then
expect(domClasses(sequentialEntry).has('active')).to.be.false;
}));
it('should set parallel button inactive', inject(function(elementRegistry) {
// given
var task = elementRegistry.get('ParallelTask');
openPopup(task);
// when
triggerAction('toggle-loop');
openPopup(task);
var parallelEntry = queryEntry('toggle-parallel-mi');
// then
expect(domClasses(parallelEntry).has('active')).to.be.false;
}));
it('should set loop characteristics type', inject(function(elementRegistry) {
// given
var task = elementRegistry.get('SequentialTask'),
businessObject = getBusinessObject(task);
openPopup(task);
// when
triggerAction('toggle-loop');
// then
var newLoopCharacteristics = businessObject.loopCharacteristics;
expect(is(newLoopCharacteristics, 'bpmn:StandardLoopCharacteristics')).to.be.true;
expect(newLoopCharacteristics.isSequential).to.be.undefined;
}));
});
describe('non-interrupting toggle', function() {
beforeEach(bootstrapModeler(diagramXMLReplace,{
modules: Object.assign(testModules, camundaModdleModule),
moddleExtensions: {
camunda: camundaPackage
}
}));
describe('start events', function() {
it('should toggle non-interrupting marker off', inject(function(bpmnReplace, elementRegistry) {
// given
var event = elementRegistry.get('StartEvent_3');
openPopup(event);
// when
triggerAction('toggle-non-interrupting');
openPopup(event);
var nonInterruptingEntry = queryEntry('toggle-non-interrupting');
// then
expect(event.businessObject.isInterrupting).to.be.true;
expect(domClasses(nonInterruptingEntry).has('active')).to.be.false;
}));
it('should toggle non-interrupting marker on', inject(function(bpmnReplace, elementRegistry) {
// given
var event = elementRegistry.get('StartEvent_6');
openPopup(event);
// when
triggerAction('toggle-non-interrupting');
openPopup(event);
var nonInterruptingEntry = queryEntry('toggle-non-interrupting');
// then
expect(event.businessObject.isInterrupting).to.be.false;
expect(domClasses(nonInterruptingEntry).has('active')).to.be.true;
}));
});
describe('boundary events', function() {
it('should toggle non-interrupting marker off', inject(function(bpmnReplace, elementRegistry) {
// given
var event = elementRegistry.get('BoundaryEvent_1');
openPopup(event);
// when
triggerAction('toggle-non-interrupting');
openPopup(event);
var nonInterruptingEntry = queryEntry('toggle-non-interrupting');
// then
expect(event.businessObject.cancelActivity).to.be.true;
expect(domClasses(nonInterruptingEntry).has('active')).to.be.false;
}));
it('should toggle non-interrupting marker on', inject(function(bpmnReplace, elementRegistry) {
// given
var event = elementRegistry.get('BoundaryEvent_2');
openPopup(event);
// when
triggerAction('toggle-non-interrupting');
openPopup(event);
var nonInterruptingEntry = queryEntry('toggle-non-interrupting');
// then
expect(event.businessObject.cancelActivity).to.be.false;
expect(domClasses(nonInterruptingEntry).has('active')).to.be.true;
}));
});
});
describe('integration', function() {
it('should toggle sequential -> undo to parallel', inject(function(elementRegistry, commandStack) {
// given
var task = elementRegistry.get('ParallelTask');
openPopup(task);
// when
triggerAction('toggle-sequential-mi');
commandStack.undo();
// then
const bo = getBusinessObject(task),
loopCharacteristics = bo.get('loopCharacteristics');
expect(is(loopCharacteristics, 'bpmn:MultiInstanceLoopCharacteristics')).to.be.true;
expect(loopCharacteristics.isSequential).to.be.false;
}));
it('should toggle parallel -> undo to parallel', inject(function(elementRegistry, commandStack) {
// given
var task = elementRegistry.get('ParallelTask');
openPopup(task);
// when
triggerAction('toggle-parallel-mi');
commandStack.undo();
// then
const bo = getBusinessObject(task),
loopCharacteristics = bo.get('loopCharacteristics');
expect(is(loopCharacteristics, 'bpmn:MultiInstanceLoopCharacteristics')).to.be.true;
expect(loopCharacteristics.isSequential).to.be.false;
}));
it('should toggle loop -> undo to parallel', inject(function(elementRegistry, commandStack) {
// given
var task = elementRegistry.get('ParallelTask');
openPopup(task);
// when
triggerAction('toggle-loop');
commandStack.undo();
// then
const bo = getBusinessObject(task),
loopCharacteristics = bo.get('loopCharacteristics');
expect(is(loopCharacteristics, 'bpmn:MultiInstanceLoopCharacteristics')).to.be.true;
expect(loopCharacteristics.isSequential).to.be.false;
}));
});
});
describe('replacing', function() {
beforeEach(bootstrapModeler(diagramXMLMarkers, { modules: testModules }));
it('should retain the loop characteristics', inject(function(elementRegistry) {
// given
var task = elementRegistry.get('SequentialTask');
openPopup(task);
// when
// replacing the task with a send task
var sendTask = triggerAction('replace-with-send-task');
// then
expect(sendTask.businessObject.loopCharacteristics).to.exist;
expect(sendTask.businessObject.loopCharacteristics.isSequential).to.be.true;
expect(is(sendTask.businessObject.loopCharacteristics, 'bpmn:MultiInstanceLoopCharacteristics')).to.be.true;
}));
it('should retain the loop characteristics for call activites',
inject(function(elementRegistry) {
// given
var task = elementRegistry.get('SequentialTask');
openPopup(task);
// when
// replacing the task with a call activity
var callActivity = triggerAction('replace-with-call-activity');
// then
expect(callActivity.businessObject.loopCharacteristics).to.exist;
expect(callActivity.businessObject.loopCharacteristics.isSequential).to.be.true;
expect(is(callActivity.businessObject.loopCharacteristics, 'bpmn:MultiInstanceLoopCharacteristics')).to.be.true;
})
);
it('should retain expanded status for sub processes',
inject(function(elementRegistry) {
// given
var subProcess = elementRegistry.get('SubProcess');
openPopup(subProcess);
// when
// replacing the expanded sub process with a transaction
var transaction = triggerAction('replace-with-transaction');
// then
expect(isExpanded(transaction)).to.equal(isExpanded(subProcess));
})
);
it('should replace sub processes -> event sub process',
inject(function(elementRegistry) {
// given
var subProcess = elementRegistry.get('SubProcess');
openPopup(subProcess);
// when
// replacing the expanded sub process with a eventSubProcess
var eventSubProcess = triggerAction('replace-with-event-subprocess');
// then
expect(eventSubProcess.businessObject.triggeredByEvent).to.be.true;
})
);
it('should replace event sub processes -> sub process',
inject(function(elementRegistry) {
// given
var eventSubProcess = elementRegistry.get('EventSubProcess');
openPopup(eventSubProcess);
// when
// replacing the expanded sub process with a eventSubProcess
var subProcess = triggerAction('replace-with-subprocess');
// then
expect(subProcess.businessObject.triggeredByEvent).to.be.false;
})
);
it('should retain the loop characteristics and the expanded status for transactions',
inject(function(elementRegistry) {
// given
var transaction = elementRegistry.get('Transaction');
openPopup(transaction);
// when
// replacing the transaction with an expanded sub process
var subProcess = triggerAction('replace-with-subprocess');
// then
expect(isExpanded(subProcess)).to.equal(isExpanded(transaction));
})
);
it('should not retain the loop characteristics morphing to an event sub process',
inject(function(bpmnFactory, elementRegistry, modeling) {
// given
var transaction = elementRegistry.get('Transaction');
modeling.updateProperties(transaction, {
loopCharacteristics: bpmnFactory.create('bpmn:MultiInstanceLoopCharacteristics', {
isParallel: true
})
});
openPopup(transaction);
// when
// replacing the transaction with an event sub process
var subProcess = triggerAction('replace-with-event-subprocess');
// then
expect(isExpanded(subProcess)).to.equal(isExpanded(transaction));
})
);
it('should retain the expanded property morphing to an event sub processes',
inject(function(elementRegistry) {
// given
var transaction = elementRegistry.get('Transaction');
openPopup(transaction);
// when
// replacing the transaction with an expanded sub process
var eventSubProcess = triggerAction('replace-with-event-subprocess');
// then
expect(isExpanded(eventSubProcess)).to.equal(isExpanded(transaction));
})
);
it('should replace sub processes -> ad hoc sub process',
inject(function(elementRegistry) {
// given
var subprocess = elementRegistry.get('SubProcess');
openPopup(subprocess);
// when
var adHocSubProcess = triggerAction('replace-with-ad-hoc-subprocess');
// then
expect(adHocSubProcess.type).to.equal('bpmn:AdHocSubProcess');
})
);
it('should replace interrupting event <-> non-interrupting event',
inject(function(elementRegistry) {
// given
const event = elementRegistry.get('MessageStartEvent');
// when
openPopup(event);
const nonInterruptingEvent = triggerAction('replace-with-non-interrupting-message-start');
// then
expect(nonInterruptingEvent.businessObject.isInterrupting, 'isInterrupting').to.be.false;
// when
openPopup(nonInterruptingEvent);
const interruptingEvent = triggerAction('replace-with-message-start');
// then
expect(interruptingEvent.businessObject.isInterrupting, 'isInterrupting').to.be.true;
})
);
});
describe('replace menu', function() {
describe('events', function() {
beforeEach(bootstrapModeler(diagramXMLReplace, { modules: testModules }));
it('should contain all except the current one',
inject(function(elementRegistry) {
// given
var startEvent = elementRegistry.get('StartEvent_1');
// when
openPopup(startEvent);
// then
expect(queryEntry('replace-with-none-start')).to.be.null;
expect(queryBodyEntries()).to.have.length(6);
})
);
it('should contain all start events inside event sub process except the current one',
inject(function(elementRegistry) {
// given
var startEvent = elementRegistry.get('StartEvent_3');
// when
openPopup(startEvent);
// then
expect(queryEntry('replace-with-non-interrupting-message-start')).to.be.null;
expect(queryEntry('replace-with-message-start')).to.exist;
expect(queryBodyEntries()).to.have.length(14);
})
);
it('should contain all non interrupting start events inside event sub process except the current one',
inject(function(bpmnReplace, elementRegistry) {
// given
var startEvent = elementRegistry.get('StartEvent_3');
var newElement = bpmnReplace.replaceElement(startEvent, {
type: 'bpmn:StartEvent',
eventDefinitionType: 'bpmn:ConditionalEventDefinition',
isInterrupting: false
});
// when
openPopup(newElement);
// then
expect(queryEntry('replace-with-conditional-start')).to.exist;
expect(queryEntry('replace-with-non-interrupting-conditional-start')).to.be.null;
expect(queryBodyEntries()).to.have.length(12);
})
);
it('should include non-interrupting toggle for non interrupting start event',
inject(function(elementRegistry) {
// given
var startEvent = elementRegistry.get('StartEvent_3');
// when
openPopup(startEvent);
// then
expect(queryEntry('toggle-non-interrupting')).to.exist;
})
);
it('should include non-interrupting toggle for interrupting start event',
inject(function(elementRegistry) {
// given
var startEvent = elementRegistry.get('StartEvent_6');
// when
openPopup(startEvent);
// then
expect(queryEntry('toggle-non-interrupting')).to.exist;
})
);
it('should NOT include non-interrupting toggle for start events that must be interrupting',
inject(function(bpmnReplace, elementRegistry) {
// given
var startEvent = elementRegistry.get('StartEvent_3');
var newElement = bpmnReplace.replaceElement(startEvent, {
type: 'bpmn:StartEvent'
});
// when
openPopup(newElement);
// then
expect(queryEntry('toggle-non-interrupting')).not.to.exist;
})
);
it('should contain only start event, end event and intermediate throw event inside sub process except the current one',
inject(function(elementRegistry) {
// given
var startEvent = elementRegistry.get('StartEvent_2');
// when
openPopup(startEvent);
// then
expect(queryEntry('replace-with-message-start')).to.be.null;
expect(queryEntry('replace-with-none-end')).to.exist;
expect(queryEntry('replace-with-none-intermediate-throwing')).to.exist;
expect(queryBodyEntries()).to.have.length(2);
})
);
it('should contain all intermediate events except the current one',
inject(function(elementRegistry) {
// given
var intermediateEvent = elementRegistry.get('IntermediateThrowEvent_1');
// when
openPopup(intermediateEvent);
// then
expect(queryEntry('replace-with-none-intermediate-throw')).to.be.null;
expect(queryBodyEntries()).to.have.length(12);
})
);
it('should contain all end events except the current one',
inject(function(elementRegistry) {
// given
var endEvent = elementRegistry.get('EndEvent_1');
// when
openPopup(endEvent);
// then
expect(queryEntry('replace-with-none-end')).to.be.null;
expect(queryBodyEntries()).to.have.length(8);
})
);
it('should show corresponding "non-interrupting" event',
inject(function(elementRegistry) {
// given
var messageStartEvent = elementRegistry.get('StartEvent_6');
// when
openPopup(messageStartEvent);
// then
expect(queryEntry('replace-with-message-start')).to.be.null;
expect(queryEntry('replace-with-non-interrupting-message-start')).exist;
})
);
it('should show corresponding variants for a timer event',
inject(function(elementRegistry) {
// given
var timerStartEvent = elementRegistry.get('StartEvent_4');
// when
openPopup(timerStartEvent);
// then
expect(queryEntry('replace-with-timer-start')).to.be.null;
expect(queryEntry('replace-with-timer-intermediate-catch')).exist;
})
);
it('should show corresponding variants for a message event',
inject(function(elementRegistry) {
// given
var messageStartEvent = elementRegistry.get('StartEvent_5');
// when
openPopup(messageStartEvent);
// then
expect(queryEntry('replace-with-message-start')).to.be.null;
expect(queryEntry('replace-with-message-intermediate-catch')).exist;
expect(queryEntry('replace-with-message-intermediate-throw')).exist;
expect(queryEntry('replace-with-message-end')).exist;
})
);
it('should show corresponding variants for a compensation event',
inject(function(elementRegistry) {
// given
var messageStartEvent = elementRegistry.get('CompensationEvent');
// when
openPopup(messageStartEvent);
// then
expect(queryEntry('replace-with-compensation-start')).to.be.null;
expect(queryEntry('replace-with-compensation-intermediate-throw')).to.be.null;
expect(queryEntry('replace-with-compensation-end')).exist;
})
);
it('should show corresponding variants for a conditional event',
inject(function(elementRegistry) {
// given
var messageStartEvent = elementRegistry.get('ConditionalEvent');
// when
openPopup(messageStartEvent);
// then
expect(queryEntry('replace-with-conditional-start')).exist;
expect(queryEntry('replace-with-conditional-intermediate-catch')).to.be.null;
})
);
it('should show corresponding variants for an error event',
inject(function(elementRegistry) {
// given
var messageStartEvent = elementRegistry.get('ErrorEvent');
// when
openPopup(messageStartEvent);
// then
expect(queryEntry('replace-with-error-start')).exist;
expect(queryEntry('replace-with-error-end')).to.be.null;
})
);
it('should NOT show corresponding start event variants for a message event in subprocess',
inject(function(elementRegistry) {
// given
var messageStartEvent = elementRegistry.get('IntermediateCatchMessageEvent');
// when
openPopup(messageStartEvent);
// then
expect(queryEntry('replace-with-message-start')).to.be.null;
expect(queryEntry('replace-with-message-intermediate-catch')).to.be.null;
expect(queryEntry('replace-with-message-intermediate-throw')).exist;
expect(queryEntry('replace-with-message-end')).exist;
})
);
});
describe('cancel event definition', function() {
var diagramXML = require('../../../fixtures/bpmn/features/replace/cancel-events.bpmn');
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
describe('for end events', function() {
it('should contain cancel event replace option in transaction',
inject(function(elementRegistry) {
// given
var endEvent = elementRegistry.get('EndEvent_3');
// when
openPopup(endEvent);
// then
expect(queryBodyEntries()).to.have.length(11);
expect(queryEntry('replace-with-cancel-end')).to.exist;
})
);
it('should NOT contain cancel event replace option in transaction when already set',
inject(function(elementRegistry) {
// given
var endEvent = elementRegistry.get('EndEvent_1');
// when
openPopup(endEvent);
// then
expect(queryBodyEntries()).to.have.length(9);
expect(queryEntry('replace-with-cancel-end')).to.be.null;
})
);
it('should NOT contain cancel event replace option outside transaction',
inject(function(elementRegistry) {
// given
var endEvent = elementRegistry.get('EndEvent_2');
// when
openPopup(endEvent);
// then
expect(queryBodyEntries()).to.have.length(8);
expect(queryEntry('replace-with-cancel-end')).to.be.null;
})
);
});
describe('for boundary events', function() {
it('should contain cancel event replace option attachted to Transaction',
inject(function(elementRegistry) {
// given
var boundaryEvent = elementRegistry.get('BoundaryEvent_1');
// when
openPopup(boundaryEvent);
// then
expect(queryBodyEntries()).to.have.length(13);
expect(queryEntry('replace-with-cancel-boundary')).to.exist;
})
);
it('should NOT contain cancel event replace option attached to SubProcess',
inject(function(elementRegistry) {
// given
var boundaryEvent = elementRegistry.get('BoundaryEvent_2');
// when
openPopup(boundaryEvent);
// then
expect(queryBodyEntries()).to.have.length(12);
expect(queryEntry('replace-with-cancel-boundary')).to.be.null;
})
);
it('should NOT contain cancel event replace option attached to Activity',
inject(function(elementRegistry) {
// given
var boundaryEvent = elementRegistry.get('BoundaryEvent_3');
// when
openPopup(boundaryEvent);
// then
expect(queryBodyEntries()).to.have.length(12);
expect(queryEntry('replace-with-cancel-boundary')).to.be.null;
})
);
});
});
describe('boundary events', function() {
beforeEach(bootstrapModeler(diagramXMLReplace, { modules: testModules }));
it('should contain all boundary events (except for cancel and currently active) for an interrupting boundary event',
inject(function(elementRegistry) {
// given
var boundaryEvent = elementRegistry.get('BoundaryEvent_1');
// when
openPopup(boundaryEvent, 40);
// then
expect(queryEntry('replace-with-conditional-intermediate-catch')).to.be.null;
expect(queryEntry('replace-with-cancel-boundary')).to.be.null;
expect(queryBodyEntries()).to.have.length(11);
})
);
it('should contain all boundary events (except for cancel and currently active) for a non interrupting boundary event',
inject(function(elementRegistry) {
// given
var boundaryEvent = elementRegistry.get('BoundaryEvent_2');
// when
openPopup(boundaryEvent, 40);
// then
expect(queryEntry('replace-with-non-interrupting-message-intermediate-catch')).to.be.null;
expect(queryEntry('replace-with-cancel-boundary')).to.be.null;
expect(queryBodyEntries()).to.have.length(11);
})
);
it('should contain compensation boundary event',
inject(function(elementRegistry) {
// given
var boundaryEvent = elementRegistry.get('BoundaryEvent_1');
// when
openPopup(boundaryEvent, 40);
// then
expect(queryEntry('replace-with-compensation-boundary')).to.exist;
})
);
});
describe('default flows', function() {
var diagramXML = require('./ReplaceMenuProvider.defaultFlows.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
it('should show default replace option [gateway]', inject(function(elementRegistry) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow_3');
// when
openPopup(sequenceFlow);
// then
expect(queryBodyEntries()).to.have.length(1);
}));
it('should show Default replace option [task]', inject(function(elementRegistry) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow_15f5knn');
// when
openPopup(sequenceFlow);
// then
expect(queryBodyEntries()).to.have.length(2);
}));
it('should NOT show default replace option', inject(function(elementRegistry) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow_4');
// when
openPopup(sequenceFlow);
// then
expect(queryBodyEntries()).to.have.length(0);
}));
});
describe('default flows from inclusive gateways', function() {
var diagramXML = require('./ReplaceMenuProvider.defaultFlowsFromInclusiveGateways.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
it('should show default replace option', inject(function(elementRegistry) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow_2');
// when
openPopup(sequenceFlow);
var sequenceFlowEntry = queryEntry('replace-with-sequence-flow'),
defaultFlowEntry = queryEntry('replace-with-default-flow');
// then
expect(sequenceFlowEntry).not.to.exist;
expect(defaultFlowEntry).to.exist;
}));
it('should NOT show default replace option', inject(function(elementRegistry) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow_1');
// when
openPopup(sequenceFlow);
var sequenceFlowEntry = queryEntry('replace-with-sequence-flow'),
defaultFlowEntry = queryEntry('replace-with-default-flow');
// then
expect(sequenceFlowEntry).to.exist;
expect(defaultFlowEntry).not.to.exist;
}));
});
describe('default flows from complex gateways', function() {
var diagramXML = require('./ReplaceMenuProvider.defaultFlowsFromComplexGateways.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
it('should show default replace option', inject(function(elementRegistry) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow_2');
// when
openPopup(sequenceFlow);
var sequenceFlowEntry = queryEntry('replace-with-sequence-flow'),
defaultFlowEntry = queryEntry('replace-with-default-flow');
// then
expect(sequenceFlowEntry).not.to.exist;
expect(defaultFlowEntry).to.exist;
}));
it('should NOT show default replace option', inject(function(elementRegistry) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow_1');
// when
openPopup(sequenceFlow);
var sequenceFlowEntry = queryEntry('replace-with-sequence-flow'),
defaultFlowEntry = queryEntry('replace-with-default-flow');
// then
expect(sequenceFlowEntry).to.exist;
expect(defaultFlowEntry).not.to.exist;
}));
});
describe('conditional flows', function() {
var diagramXML = require('./ReplaceMenuProvider.conditionalFlows.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
it('should show ConditionalFlow replace option', inject(function(elementRegistry) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow_3');
// when
openPopup(sequenceFlow);
var conditionalFlowEntry = queryEntry('replace-with-conditional-flow');
// then
expect(conditionalFlowEntry).to.exist;
expect(queryBodyEntries()).to.have.length(2);
}));
it('should NOT show ConditionalFlow replace option', inject(function(elementRegistry) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow_1');
// when
openPopup(sequenceFlow);
// then
expect(queryBodyEntries()).to.have.length(0);
}));
});
describe('compensate activities', function() {
var diagramXML = require('./ReplaceMenuProvider.compensation-activity.bpmn');
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
it('options should include subProcesses and callActivity', inject(function(elementRegistry) {
// given
var taskElement = elementRegistry.get('Task_1');
// when
openPopup(taskElement);
var callActivityEntry = queryEntry('replace-with-call-activity'),
subProcessEntry = queryEntry('replace-with-collapsed-subprocess');
// then
expect(callActivityEntry).to.exist;
expect(subProcessEntry).to.exist;
}));
});
describe('subprocesses', function() {
var diagramXML = require('./ReplaceMenuProvider.subProcesses.bpmn');
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
describe('subprocess', function() {
it('options do not include subprocess itself', inject(function(elementRegistry) {
// given
var collapsedSubProcess = elementRegistry.get('SubProcess_1');
// when
openPopup(collapsedSubProcess);
var entry = queryEntry('replace-with-subprocess');
// then
expect(entry).not.to.exist;
}));
it('options include collapsed subprocess', inject(function(elementRegistry) {
// given
var collapsedSubProcess = elementRegistry.get('SubProcess_1');
// when
openPopup(collapsedSubProcess);
var entry = queryEntry('replace-with-collapsed-subprocess');
// then
expect(entry).to.exist;
}));
it('options include ad hoc subprocess', inject(function(elementRegistry) {
// given
var collapsedSubProcess = elementRegistry.get('SubProcess_1');
// when
openPopup(collapsedSubProcess);
var entry = queryEntry('replace-with-ad-hoc-subprocess');
// then
expect(entry).to.exist;
}));
it('options do not include collapsed ad hoc subprocess', inject(function(elementRegistry) {
// given
var collapsedSubProcess = elementRegistry.get('SubProcess_1');
// when
openPopup(collapsedSubProcess);
var entry = queryEntry('replace-with-collapsed-ad-hoc-subprocess');
// then
expect(entry).not.to.exist;
}));
});
describe('ad hoc subprocess', function() {
it('options do not include ad hoc subprocess itself', inject(function(elementRegistry) {
// given
var collapsedSubProcess = elementRegistry.get('AdhocSubProcess_1');
// when
openPopup(collapsedSubProcess);
var entry = queryEntry('replace-with-ad-hoc-subprocess');
// then
expect(entry).not.to.exist;
}));
it('options include collapsed subprocess', inject(function(elementRegistry) {
// given
var collapsedSubProcess = elementRegistry.get('AdhocSubProcess_1');
// when
openPopup(collapsedSubProcess);
var entry = queryEntry('replace-with-collapsed-ad-hoc-subprocess');
// then
expect(entry).to.exist;
}));
it('options include subprocess', inject(function(elementRegistry) {
// given
var collapsedSubProcess = elementRegistry.get('AdhocSubProcess_1');
// when
openPopup(collapsedSubProcess);
var entry = queryEntry('replace-with-subprocess');
// then
expect(entry).to.exist;
}));
it('options do not include collapsed subprocess', inject(function(elementRegistry) {
// given
var collapsedSubProcess = elementRegistry.get('AdhocSubProcess_1');
// when
openPopup(collapsedSubProcess);
var entry = queryEntry('replace-with-collapsed-subprocess');
// then
expect(entry).not.to.exist;
}));
});
});
describe('collapsed subprocesses', function() {
var diagramXML = require('./ReplaceMenuProvider.collapsedSubProcess.bpmn');
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
describe('collapsed subprocess', function() {
it('options do not include collapsed subprocess itself', inject(function(elementRegistry) {
// given
var collapsedSubProcess = elementRegistry.get('Task_1');
// when
openPopup(collapsedSubProcess);
var entry = queryEntry('replace-with-collapsed-subprocess');
// then
expect(entry).not.to.exist;
}));
it('options include collapsed ad hoc subprocess', inject(function(elementRegistry) {
// given
var collapsedSubProcess = elementRegistry.get('Task_1');
// when
openPopup(collapsedSubProcess);
var entry = queryEntry('replace-with-collapsed-ad-hoc-subprocess');
// then
expect(entry).to.exist;
}));
it('options include expanded subprocess', inject(function(elementRegistry) {
// given
var collapsedSubProcess = elementRegistry.get('Task_1');
// when
openPopup(collapsedSubProcess);
var entry = queryEntry('replace-with-expanded-subprocess');
// then
expect(entry).to.exist;
}));
it('options do not include ad hoc subprocess', inject(function(elementRegistry) {
// given
var collapsedSubProcess = elementRegistry.get('Task_1');
// when
openPopup(collapsedSubProcess);
var entry = queryEntry('replace-with-ad-hoc-subprocess');
// then
expect(entry).not.to.exist;
}));
});
describe('collapsed ad hoc subprocess', function() {
it('options do not include collapsed ad hoc subprocess itself', inject(function(elementRegistry) {
// given
var collapsedSubProcess = elementRegistry.get('AdhocSubProcess_1');
// when
openPopup(collapsedSubProcess);
var entry = queryEntry('replace-with-collapsed-ad-hoc-subprocess');
// then
expect(entry).not.to.exist;
}));
it('options include collapsed subprocess', inject(function(elementRegistry) {
// given
var collapsedSubProcess = elementRegistry.get('AdhocSubProcess_1');
// when
openPopup(collapsedSubProcess);
var entry = queryEntry('replace-with-collapsed-subprocess');
// then
expect(entry).to.exist;
}));
it('options include expanded ad hoc subprocess', inject(function(elementRegistry) {
// given
var collapsedSubProcess = elementRegistry.get('AdhocSubProcess_1');
// when
openPopup(collapsedSubProcess);
var entry = queryEntry('replace-with-ad-hoc-subprocess');
// then
expect(entry).to.exist;
}));
it('options do not include expanded subprocess', inject(function(elementRegistry) {
// given
var collapsedSubProcess = elementRegistry.get('AdhocSubProcess_1');
// when
openPopup(collapsedSubProcess);
var entry = queryEntry('replace-with-subprocess');
// then
expect(entry).not.to.exist;
}));
});
});
describe('pools', function() {
var diagramXML = require('./ReplaceMenuProvider.pools.bpmn');
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
it('should indicate removing content', inject(function(elementRegistry) {
// given
var expandedPool = elementRegistry.get('Participant_1');
// when
openPopup(expandedPool);
var emptyPoolLabel = queryEntryLabel('replace-with-collapsed-pool');
// then
expect(emptyPoolLabel).to.exist;
expect(emptyPoolLabel.textContent).to.eql('Empty pool/participant (removes content)');
}));
it('should NOT indicate removing content', inject(function(elementRegistry) {
// given
var expandedPool = elementRegistry.get('Participant_2');
// when
openPopup(expandedPool);
var emptyPoolLabel = queryEntryLabel('replace-with-collapsed-pool');
// then
expect(emptyPoolLabel).to.exist;
expect(emptyPoolLabel.textContent).to.eql('Empty pool/participant');
}));
});
describe('data object', function() {
beforeEach(bootstrapModeler(diagramXMLDataElements, { modules: testModules }));
it('should only contain data store reference', inject(function(elementRegistry) {
// given
var dataObjectReference = elementRegistry.get('DataObjectReference_1');
// when
openPopup(dataObjectReference);
// then
expect(queryBodyEntries()).to.have.length(1);
expect(queryEntry('toggle-is-collection')).to.exist;
expect(queryEntry('replace-with-data-store-reference')).to.exist;
expect(queryEntry('replace-with-data-object-reference')).to.be.null;
}));
it('should handle missing dataObjectRef', inject(function(elementRegistry) {
// given
var dataObjectReference = elementRegistry.get('DataObjectReference_NO_DataObject');
// when
openPopup(dataObjectReference);
// then
expect(queryEntry('toggle-is-collection')).not.to.exist;
}));
});
describe('data store', function() {
beforeEach(bootstrapModeler(diagramXMLDataElements, { modules: testModules }));
it('should only contain data object reference', inject(function(elementRegistry) {
// given
var dataStoreReference = elementRegistry.get('DataStoreReference_1');
// when
openPopup(dataStoreReference);
// then
expect(queryBodyEntries()).to.have.length(1);
expect(queryEntry('toggle-is-collection')).to.be.null;
expect(queryEntry('replace-with-data-store-reference')).to.be.null;
expect(queryEntry('replace-with-data-object-reference')).to.exist;
}));
it('should handle missing dataStoreRef', inject(function(elementRegistry) {
// given
var dataStoreReference = elementRegistry.get('DataStoreReference_NO_DataStore');
// when
openPopup(dataStoreReference);
// then
expect(queryEntry('toggle-is-collection')).to.be.null;
}));
});
describe('data store positioned against participant', function() {
beforeEach(bootstrapModeler(diagramXMLDataStoresPositionedAgainstParticipant, { modules: testModules }));
it('should only contain data object reference', inject(function(elementRegistry) {
// given
var dataStoreReferenceWithinParticipant = elementRegistry.get('DataStoreReference_0');
// when
openPopup(dataStoreReferenceWithinParticipant);
// then
expect(queryBodyEntries()).to.have.length(1);
expect(queryEntry('replace-with-data-object-reference')).to.exist;
}));
it('should contain no reference', inject(function(elementRegistry) {
// given
var dataStoreReferenceOutsideParticipant = elementRegistry.get('DataStoreReference_1');
// when
openPopup(dataStoreReferenceOutsideParticipant);
// then
expect(queryBodyEntries()).to.have.length(0);
expect(queryEntry('replace-with-data-object-reference')).to.be.null;
}));
});
});
describe('a11y', function() {
beforeEach(bootstrapModeler(diagramXMLReplace, { modules: testModules }));
it('should report no violations', inject(async function(elementRegistry) {
// given
const startEvent = elementRegistry.get('StartEvent_1');
// when
openPopup(startEvent);
// then
const container = getMenuContainer();
await expectToBeAccessible(container);
}));
});
describe('integration', function() {
describe('default flows', function() {
var diagramXML = require('./ReplaceMenuProvider.defaultFlows.bpmn');
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
it('should replace SequenceFlow with DefaultFlow [gateway]', inject(function(elementRegistry) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow_3');
// when
openPopup(sequenceFlow);
triggerAction('replace-with-default-flow');
var gateway = elementRegistry.get('ExclusiveGateway_1');
// then
expect(gateway.businessObject.default).to.equal(sequenceFlow.businessObject);
}));
it('should replace SequenceFlow with DefaultFlow [task]', inject(function(elementRegistry) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow_15f5knn');
// when
openPopup(sequenceFlow);
triggerAction('replace-with-default-flow');
var task = elementRegistry.get('Task_1ei94kl');
// then
expect(task.businessObject.default).to.equal(sequenceFlow.businessObject);
}));
it('should morph DefaultFlow into a SequenceFlow [task]', inject(function(elementRegistry) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow_15f5knn');
openPopup(sequenceFlow);
triggerAction('replace-with-default-flow');
// when
openPopup(sequenceFlow);
triggerAction('replace-with-sequence-flow');
var task = elementRegistry.get('Task_1ei94kl');
// then
expect(task.businessObject.default).not.to.exist;
}));
it('should morph DefaultFlow into a SequenceFlow [task] -> undo',
inject(function(elementRegistry, commandStack) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow_15f5knn');
openPopup(sequenceFlow);
triggerAction('replace-with-default-flow');
// when
openPopup(sequenceFlow);
triggerAction('replace-with-sequence-flow');
commandStack.undo();
var task = elementRegistry.get('Task_1ei94kl');
// then
expect(task.businessObject.default).to.equal(sequenceFlow.businessObject);
})
);
it('should morph DefaultFlow into a ConditionalFlow [task]', inject(function(elementRegistry) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow_15f5knn'),
task = elementRegistry.get('Task_1ei94kl');
openPopup(sequenceFlow);
triggerAction('replace-with-default-flow');
// when
openPopup(sequenceFlow);
triggerAction('replace-with-conditional-flow');
// then
expect(task.businessObject.default).not.to.exist;
}));
it('should morph DefaultFlow into a ConditionalFlow [task] -> undo',
inject(function(elementRegistry, commandStack) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow_15f5knn'),
task = elementRegistry.get('Task_1ei94kl');
openPopup(sequenceFlow);
triggerAction('replace-with-default-flow');
// when
openPopup(sequenceFlow);
triggerAction('replace-with-conditional-flow');
commandStack.undo();
// then
expect(task.businessObject.default).to.equal(sequenceFlow.businessObject);
})
);
it('should replace SequenceFlow with DefaultFlow [gateway] -> undo',
inject(function(elementRegistry, commandStack) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow_3'),
gateway = elementRegistry.get('ExclusiveGateway_1');
// when
openPopup(sequenceFlow);
triggerAction('replace-with-default-flow');
commandStack.undo();
// then
expect(gateway.businessObject.default).not.to.exist;
})
);
it('should replace SequenceFlow with DefaultFlow [task] -> undo',
inject(function(elementRegistry, commandStack) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow_15f5knn');
// when
openPopup(sequenceFlow);
triggerAction('replace-with-default-flow');
commandStack.undo();
var task = elementRegistry.get('Task_1ei94kl');
// then
expect(task.businessObject.default).not.to.exist;
})
);
it('should only have one DefaultFlow', inject(function(elementRegistry) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow_1'),
sequenceFlow3 = elementRegistry.get('SequenceFlow_3');
// when
// trigger morphing sequenceFlow3 to default flow
openPopup(sequenceFlow3);
triggerAction('replace-with-default-flow');
// trigger morphing sequenceFlow to default flow
openPopup(sequenceFlow);
triggerAction('replace-with-default-flow');
var gateway = elementRegistry.get('ExclusiveGateway_1');
// then
expect(gateway.businessObject.default).to.equal(sequenceFlow.businessObject);
}));
it('should replace DefaultFlow with SequenceFlow when changing source',
inject(function(elementRegistry, modeling) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow_1'),
task = elementRegistry.get('Task_2');
openPopup(sequenceFlow);
triggerAction('replace-with-default-flow');
// when
modeling.reconnectStart(sequenceFlow, task, [
{ x: 686, y: 267, original: { x: 686, y: 307 } },
{ x: 686, y: 207, original: { x: 686, y: 187 } }
]);
var gateway = elementRegistry.get('ExclusiveGateway_1');
// then
expect(gateway.businessObject.default).not.to.exist;
})
);
it('should replace DefaultFlow with SequenceFlow when changing source -> undo',
inject(function(elementRegistry, modeling, commandStack) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow_1'),
task = elementRegistry.get('Task_2');
openPopup(sequenceFlow);
triggerAction('replace-with-default-flow');
// when
modeling.reconnectStart(sequenceFlow, task, [
{ x: 686, y: 267, original: { x: 686, y: 307 } },
{ x: 686, y: 207, original: { x: 686, y: 187 } }
]);
commandStack.undo();
var gateway = elementRegistry.get('ExclusiveGateway_1');
// then
expect(gateway.businessObject.default).equal(sequenceFlow.businessObject);
})
);
[
'bpmn:Task',
'bpmn:EndEvent',
'bpmn:IntermediateThrowEvent',
'bpmn:IntermediateCatchEvent'
].forEach(function(type) {
it('should keep DefaultFlow when changing target to ' + type,
inject(function(elementRegistry, elementFactory, canvas, modeling) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow_1'),
root = canvas.getRootElement();
var intermediateEvent = elementFactory.createShape({ type: type });
modeling.createShape(intermediateEvent, { x: 686, y: 50 }, root);
openPopup(sequenceFlow);
triggerAction('replace-with-default-flow');
// when
modeling.reconnectEnd(sequenceFlow, intermediateEvent, [
{ x: 686, y: 267, original: { x: 686, y: 307 } },
{ x: 686, y: 50, original: { x: 686, y: 75 } }
]);
var gateway = elementRegistry.get('ExclusiveGateway_1');
// then
expect(gateway.businessObject.default).to.exist;
})
);
});
it('should replace DefaultFlow with SequenceFlow when changing target -> undo',
inject(function(elementRegistry, elementFactory, canvas, modeling, commandStack) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow_1'),
rootElement = canvas.getRootElement();
var intermediateEvent = elementFactory.createShape({ type: 'bpmn:StartEvent' });
modeling.createShape(intermediateEvent, { x: 686, y: 50 }, rootElement);
openPopup(sequenceFlow);
triggerAction('replace-with-default-flow');
// when
modeling.reconnectEnd(sequenceFlow, intermediateEvent, [
{ x: 686, y: 267, original: { x: 686, y: 307 } },
{ x: 686, y: 50, original: { x: 686, y: 75 } }
]);
commandStack.undo();
var gateway = elementRegistry.get('ExclusiveGateway_1');
// then
expect(gateway.businessObject.default).equal(sequenceFlow.businessObject);
})
);
it('should keep DefaultFlow when morphing Gateway', inject(function(elementRegistry, bpmnReplace) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow_3'),
exclusiveGateway = elementRegistry.get('ExclusiveGateway_1');
// when
openPopup(sequenceFlow);
triggerAction('replace-with-default-flow');
var inclusiveGateway = bpmnReplace.replaceElement(exclusiveGateway, { type: 'bpmn:InclusiveGateway' });
// then
expect(inclusiveGateway.businessObject.default).to.equal(sequenceFlow.businessObject);
}));
it('should keep DefaultFlow when morphing Task', inject(
function(elementRegistry, bpmnReplace) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow_15f5knn'),
task = elementRegistry.get('Task_1ei94kl');
// when
openPopup(sequenceFlow);
// trigger DefaultFlow replacement
triggerAction('replace-with-default-flow');
var sendTask = bpmnReplace.replaceElement(task, { type: 'bpmn:SendTask' });
// then
expect(sendTask.businessObject.default).to.equal(sequenceFlow.businessObject);
})
);
it('should keep DefaultFlow when morphing Gateway -> undo',
inject(function(elementRegistry, bpmnReplace, commandStack) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow_3'),
exclusiveGateway = elementRegistry.get('ExclusiveGateway_1');
// when
openPopup(sequenceFlow);
triggerAction('replace-with-default-flow');
bpmnReplace.replaceElement(exclusiveGateway, { type: 'bpmn:InclusiveGateway' });
commandStack.undo();
// then
expect(exclusiveGateway.businessObject.default).to.equal(sequenceFlow.businessObject);
})
);
it('should remove any conditionExpression when morphing to DefaultFlow',
inject(function(elementRegistry, modeling, moddle) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow_3'),
exclusiveGateway = elementRegistry.get('ExclusiveGateway_1');
var conditionExpression = moddle.create('bpmn:FormalExpression', {
body: ''
});
modeling.updateProperties(sequenceFlow, {
conditionExpression: conditionExpression
});
// when
openPopup(sequenceFlow);
// trigger DefaultFlow replacement
triggerAction('replace-with-default-flow');
// then
expect(exclusiveGateway.businessObject.default).to.equal(sequenceFlow.businessObject);
expect(sequenceFlow.businessObject.conditionExpression).not.to.exist;
})
);
it('should remove any conditionExpression when morphing to DefaultFlow -> undo',
inject(function(elementRegistry, modeling, moddle, commandStack) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow_3'),
exclusiveGateway = elementRegistry.get('ExclusiveGateway_1');
var conditionExpression = moddle.create('bpmn:FormalExpression', { body: '' });
modeling.updateProperties(sequenceFlow, { conditionExpression: conditionExpression });
// when
openPopup(sequenceFlow);
// trigger DefaultFlow replacement
triggerAction('replace-with-default-flow');
commandStack.undo();
// then
expect(exclusiveGateway.businessObject.default).not.to.exist;
expect(sequenceFlow.businessObject.conditionExpression).to.equal(conditionExpression);
})
);
});
describe('conditional flows', function() {
var diagramXML = require('./ReplaceMenuProvider.conditionalFlows.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
it('should morph into a ConditionalFlow', inject(function(elementRegistry) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow_2');
// when
openPopup(sequenceFlow);
triggerAction('replace-with-conditional-flow');
// then
expect(sequenceFlow.businessObject.conditionExpression.$type).to.equal('bpmn:FormalExpression');
}));
it('should morph into a ConditionalFlow -> undo', inject(
function(elementRegistry, commandStack) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow_2');
// when
openPopup(sequenceFlow);
triggerAction('replace-with-conditional-flow');
commandStack.undo();
// then
expect(sequenceFlow.businessObject.conditionExpression).not.to.exist;
}
));
it('should morph back into a SequenceFlow', inject(function(elementRegistry) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow_2');
// when
openPopup(sequenceFlow);
// trigger ConditionalFlow replacement
triggerAction('replace-with-conditional-flow');
openPopup(sequenceFlow);
// replace with SequenceFlow
triggerAction('replace-with-sequence-flow');
// then
expect(sequenceFlow.businessObject.conditionExpression).not.to.exist;
}));
it('should replace ConditionalFlow with SequenceFlow when changing source',
inject(function(elementRegistry, modeling) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow_3'),
startEvent = elementRegistry.get('StartEvent_1');
// when
openPopup(sequenceFlow);
triggerAction('replace-with-conditional-flow');
// when
modeling.reconnectStart(sequenceFlow, startEvent, [
{ x: 196, y: 197, original: { x: 178, y: 197 } },
{ x: 497, y: 278, original: { x: 547, y: 278 } }
]);
// then
expect(sequenceFlow.businessObject.conditionExpression).not.to.exist;
})
);
it('should replace ConditionalFlow with SequenceFlow when changing source -> undo',
inject(function(elementRegistry, modeling, commandStack) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow_3'),
startEvent = elementRegistry.get('StartEvent_1');
// when
openPopup(sequenceFlow);
triggerAction('replace-with-conditional-flow');
// when
modeling.reconnectStart(sequenceFlow, startEvent, [
{ x: 196, y: 197, original: { x: 178, y: 197 } },
{ x: 497, y: 278, original: { x: 547, y: 278 } }
]);
commandStack.undo();
// then
expect(sequenceFlow.businessObject.conditionExpression.$type).to.equal('bpmn:FormalExpression');
})
);
[
'bpmn:Task',
'bpmn:EndEvent',
'bpmn:IntermediateThrowEvent',
'bpmn:IntermediateCatchEvent'
].forEach(function(type) {
it('should keep ConditionalFlow when changing target to ' + type,
inject(function(elementRegistry, elementFactory, canvas, modeling) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow_3'),
root = canvas.getRootElement(),
intermediateEvent = elementFactory.createShape({ type: type });
modeling.createShape(intermediateEvent, { x: 497, y: 197 }, root);
openPopup(sequenceFlow);
triggerAction('replace-with-conditional-flow');
// when
modeling.reconnectEnd(sequenceFlow, intermediateEvent, [
{ x: 389, y: 197, original: { x: 389, y: 197 } },
{ x: 497, y: 197, original: { x: 497, y: 197 } }
]);
// then
expect(sequenceFlow.businessObject.conditionExpression).to.exist;
})
);
});
it('should replace ConditionalFlow with SequenceFlow when changing target -> undo',
inject(function(elementRegistry, elementFactory, canvas, modeling, commandStack) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow_3'),
root = canvas.getRootElement(),
intermediateEvent = elementFactory.createShape({ type: 'bpmn:StartEvent' });
modeling.createShape(intermediateEvent, { x: 497, y: 197 }, root);
openPopup(sequenceFlow);
// trigger ConditionalFlow replacement
triggerAction('replace-with-conditional-flow');
// when
modeling.reconnectEnd(sequenceFlow, intermediateEvent, [
{ x: 389, y: 197, original: { x: 389, y: 197 } },
{ x: 497, y: 197, original: { x: 497, y: 197 } }
]);
commandStack.undo();
// then
expect(sequenceFlow.businessObject.conditionExpression.$type).to.equal('bpmn:FormalExpression');
})
);
});
describe('adhoc sub process', function() {
var diagramXML = require('./ReplaceMenuProvider.subProcesses.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules.concat(autoResizeModule)
}));
describe('sub process -> adhoc', function() {
it('should not resize', inject(function(elementRegistry, modeling) {
// given
var subProcess = elementRegistry.get('SubProcess_1');
// when
openPopup(subProcess);
const adHocSubProcess = triggerAction('replace-with-ad-hoc-subprocess');
// then
const sizeChanged = didSizeChange(subProcess, adHocSubProcess);
expect(sizeChanged).to.be.false;
}));
it('should not lay out connection', inject(function(elementRegistry, modeling) {
// given
var subProcess = elementRegistry.get('SubProcess_1');
var layoutConnectionSpy = sinon.spy(modeling, 'layoutConnection');
// when
openPopup(subProcess);
triggerAction('replace-with-ad-hoc-subprocess');
// then
expect(layoutConnectionSpy).not.to.have.been.called;
}));
});
describe('adhoc -> sub process', function() {
it('should not resize', inject(function(elementRegistry, modeling) {
// given
var adhocSubProcess = elementRegistry.get('AdhocSubProcess_1');
// when
openPopup(adhocSubProcess);
const subprocess = triggerAction('replace-with-subprocess');
// then
const sizeChanged = didSizeChange(adhocSubProcess, subprocess);
expect(sizeChanged).to.be.false;
}));
it('should not lay out connection', inject(function(elementRegistry, modeling) {
// given
var adhocSubProcess = elementRegistry.get('AdhocSubProcess_1');
var layoutConnectionSpy = sinon.spy(modeling, 'layoutConnection');
// when
openPopup(adhocSubProcess);
triggerAction('replace-with-subprocess');
// then
expect(layoutConnectionSpy).not.to.have.been.called;
}));
});
});
describe('events', function() {
var diagramXML = require('./ReplaceMenuProvider.events.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
it('should set default link name for a link catch event', inject(function(elementRegistry) {
// given
var event = elementRegistry.get('IntermediateEvent');
// when
openPopup(event);
triggerAction('replace-with-link-intermediate-catch');
// then
event = elementRegistry.get('IntermediateEvent');
expect(event).to.exist;
expect(is(event, 'bpmn:IntermediateCatchEvent'), 'is not a catch event').to.be.true;
var eventBo = event.businessObject,
eventDefinitions = eventBo.eventDefinitions;
expect(eventDefinitions).to.exist;
expect(eventDefinitions).to.have.length(1);
var eventDefinition = eventDefinitions[ 0 ];
expect(is(eventDefinition, 'bpmn:LinkEventDefinition')).to.be.true;
expect(eventDefinition.name, 'name is not set').to.eql('');
}));
it('should set default link name for a link throw event', inject(function(elementRegistry) {
// given
var event = elementRegistry.get('IntermediateEvent');
// when
openPopup(event);
triggerAction('replace-with-link-intermediate-throw');
// then
event = elementRegistry.get('IntermediateEvent');
expect(event).to.exist;
expect(is(event, 'bpmn:IntermediateThrowEvent'), 'is not a throw event').to.be.true;
var eventBo = event.businessObject,
eventDefinitions = eventBo.eventDefinitions;
expect(eventDefinitions).to.exist;
expect(eventDefinitions).to.have.length(1);
var eventDefinition = eventDefinitions[ 0 ];
expect(is(eventDefinition, 'bpmn:LinkEventDefinition')).to.be.true;
expect(eventDefinition.name, 'name is not set').to.eql('');
}));
});
});
describe('rules', function() {
var diagramXML = require('../../../fixtures/bpmn/basic.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules.concat([ customRulesModule ])
}));
it('should get entries by default', inject(function(elementRegistry) {
// given
var startEvent = elementRegistry.get('StartEvent_1');
// when
openPopup(startEvent);
// then
expect(queryBodyEntries()).to.have.length.above(0);
}));
it('should get entries when custom rule returns true',
inject(function(elementRegistry, customRules) {
// given
var startEvent = elementRegistry.get('StartEvent_1');
customRules.addRule('shape.replace', function() {
return true;
});
// when
openPopup(startEvent);
// then
expect(queryBodyEntries()).to.have.length.above(0);
})
);
it('should get no entries when custom rule returns false',
inject(function(elementRegistry, customRules) {
// given
var startEvent = elementRegistry.get('StartEvent_1');
customRules.addRule('shape.replace', function() {
return false;
});
// when
openPopup(startEvent);
// then
expect(queryBodyEntries()).to.have.length(0);
})
);
it('should provide element to custom rules', inject(function(elementRegistry, customRules) {
// given
var startEvent = elementRegistry.get('StartEvent_1');
var actual;
customRules.addRule('shape.replace', function(context) {
actual = context.element;
});
// when
openPopup(startEvent);
// then
expect(actual).to.equal(startEvent);
}));
it('should evaluate rule once', inject(function(elementRegistry, customRules) {
// given
var callCount = 0;
var startEvent = elementRegistry.get('StartEvent_1');
customRules.addRule('shape.replace', function() {
callCount++;
});
// when
openPopup(startEvent);
// then
expect(callCount).to.equal(1);
}));
});
});
// helpers ////////////
function openPopup(element, offset) {
offset = offset || 100;
getBpmnJS().invoke(function(popupMenu) {
popupMenu.open(element, 'bpmn-replace', {
x: element.x + offset, y: element.y + offset
});
});
}
function queryEntry(id) {
var container = getMenuContainer();
return domQuery('.djs-popup [data-id="' + id + '"]', container);
}
function queryBodyEntries() {
var container = getMenuContainer();
return domQueryAll('.djs-popup .djs-popup-body .entry', container);
}
function queryEntryLabel(id) {
var entry = queryEntry(id);
return domQuery('span', entry);
}
function triggerAction(id) {
var entry = queryEntry(id);
if (!entry) {
throw new Error('entry "' + id + '" not found in replace menu');
}
var popupMenu = getBpmnJS().get('popupMenu');
return popupMenu.trigger(globalEvent(entry, { x: 0, y: 0 }));
}
function getMenuContainer() {
const popup = getBpmnJS().get('popupMenu');
return popup._current.container;
}
function didSizeChange(element, newElement) {
return element.di.bounds.width !== newElement.di.bounds.width ||
element.di.bounds.height !== newElement.di.bounds.height;
}
================================================
FILE: test/spec/features/replace/BpmnReplace.collaboration.bpmn
================================================
Task_1
EndEvent_1
================================================
FILE: test/spec/features/replace/BpmnReplace.collaboration.vertical.bpmn
================================================
Task_1
EndEvent_1
================================================
FILE: test/spec/features/replace/BpmnReplace.collapsedSubProcess.bpmn
================================================
sid-89A3F9F2-CCC8-46C7-816B-DD8AC8A98300
sid-89A3F9F2-CCC8-46C7-816B-DD8AC8A98300
sid-F06605E1-AEC1-4B39-8843-4AD3F547B557
sid-FC2ECAF5-771E-4ED3-BEF6-EFAB45E79500
sid-F06605E1-AEC1-4B39-8843-4AD3F547B557
sid-31F6EC44-E44C-4121-B4FE-BD69AF208C05
sid-EB275CF2-5EF1-44FA-B41B-71EB37CC2657
sid-EB275CF2-5EF1-44FA-B41B-71EB37CC2657
sid-FB543319-8DFB-4445-AAA3-720137FB230B
sid-FB543319-8DFB-4445-AAA3-720137FB230B
sid-B99D259B-1BD5-45FF-BD57-FB99C360BAC0
sid-472B540C-A0CD-46F4-9640-DF692EC1BFFC
sid-472B540C-A0CD-46F4-9640-DF692EC1BFFC
sid-910420B0-D11B-4F9D-B285-703D8AC0BA90
sid-A7460113-CB75-491D-817B-5E1A8C606B8C
sid-A7460113-CB75-491D-817B-5E1A8C606B8C
sid-01982395-64E8-43EF-A6D3-CDD276C312AA
sid-01982395-64E8-43EF-A6D3-CDD276C312AA
sid-910420B0-D11B-4F9D-B285-703D8AC0BA90
sid-B99D259B-1BD5-45FF-BD57-FB99C360BAC0
sid-FC2ECAF5-771E-4ED3-BEF6-EFAB45E79500
sid-5B23450F-AF5E-4519-B134-32107776BD44
sid-E71F5783-AFE7-44ED-8A9C-378C95087448
sid-E71F5783-AFE7-44ED-8A9C-378C95087448
sid-6B9741CD-D94B-41C7-A2EA-63A4C9445E16
sid-6B9741CD-D94B-41C7-A2EA-63A4C9445E16
sid-1A9DABC6-6079-4BF2-9D49-C4DC9569C519
sid-E5404926-738D-4447-87FE-FC6DD1E8BEFC
sid-E5404926-738D-4447-87FE-FC6DD1E8BEFC
sid-FED62A8F-6C3A-4BB2-8DE9-18FB0B35B50E
sid-FED62A8F-6C3A-4BB2-8DE9-18FB0B35B50E
sid-1A9DABC6-6079-4BF2-9D49-C4DC9569C519
sid-5B23450F-AF5E-4519-B134-32107776BD44
sid-31F6EC44-E44C-4121-B4FE-BD69AF208C05
sid-F7DA1903-6A1A-4858-AF4B-286A968C957F
sid-DCB98638-BEBD-4548-B501-F0E29AC71ED4
sid-DCB98638-BEBD-4548-B501-F0E29AC71ED4
sid-F7DA1903-6A1A-4858-AF4B-286A968C957F
sid-3FAE72F2-4037-4CBA-8B89-01D7FC7FF3E3
sid-3FAE72F2-4037-4CBA-8B89-01D7FC7FF3E3
================================================
FILE: test/spec/features/replace/BpmnReplace.compensation.bpmn
================================================
================================================
FILE: test/spec/features/replace/BpmnReplace.dataObjects.bpmn
================================================
DataObjectReference_IN
TaskPlaceholderProperty
DataObjectReference_OUT
================================================
FILE: test/spec/features/replace/BpmnReplace.eventSubProcesses.bpmn
================================================
SequenceFlow_1
SequenceFlow_2
SequenceFlow_3
SequenceFlow_3
SequenceFlow_4
SequenceFlow_4
SequenceFlow_1
SequenceFlow_2
SequenceFlow_094t9fa
SequenceFlow_094t9fa
================================================
FILE: test/spec/features/replace/BpmnReplace.poolMessageFlows.bpmn
================================================
================================================
FILE: test/spec/features/replace/BpmnReplace.poolMessageFlows.vertical.bpmn
================================================
================================================
FILE: test/spec/features/replace/BpmnReplaceSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import {
assign,
pick
} from 'min-dash';
import modelingModule from 'lib/features/modeling';
import replaceModule from 'lib/features/replace';
import moveModule from 'diagram-js/lib/features/move';
import coreModule from 'lib/core';
import camundaModdleModule from 'camunda-bpmn-moddle/lib';
import camundaPackage from 'camunda-bpmn-moddle/resources/camunda.json';
import {
is,
getDi
} from 'lib/util/ModelUtil';
import {
isExpanded,
isInterrupting,
isEventSubProcess,
hasErrorEventDefinition
} from 'lib/util/DiUtil';
import { getMid } from 'diagram-js/lib/layout/LayoutUtil';
describe('features/replace - bpmn replace', function() {
var testModules = [
camundaModdleModule,
coreModule,
modelingModule,
moveModule,
replaceModule
];
describe('should replace', function() {
var diagramXML = require('../../../fixtures/bpmn/features/replace/01_replace.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules,
moddleExtensions: {
camunda: camundaPackage
}
}));
it('task', inject(function(elementRegistry, bpmnReplace) {
// given
var task = elementRegistry.get('Task_1');
var newElementData = {
type: 'bpmn:UserTask'
};
// when
var newElement = bpmnReplace.replaceElement(task, newElementData);
// then
var businessObject = newElement.businessObject;
expect(newElement).to.exist;
expect(is(businessObject, 'bpmn:UserTask')).to.be.true;
}));
it('Task with new DI', inject(function(elementRegistry, bpmnReplace) {
// given
var task = elementRegistry.get('Task_1');
var taskDi = getDi(taskDi);
var newElementData = {
type: 'bpmn:UserTask'
};
// when
var newElement = bpmnReplace.replaceElement(task, newElementData);
// then
expect(newElement).to.exist;
}));
it('gateway', inject(function(elementRegistry, bpmnReplace) {
// given
var gateway = elementRegistry.get('ExclusiveGateway_1');
var newElementData = {
type: 'bpmn:InclusiveGateway'
};
// when
var newElement = bpmnReplace.replaceElement(gateway, newElementData);
// then
var businessObject = newElement.businessObject;
expect(newElement).to.exist;
expect(is(businessObject, 'bpmn:InclusiveGateway')).to.be.true;
expect(newElement.di.isMarkerVisible).to.not.exist;
}));
it('gateway and set marker', inject(function(elementRegistry, bpmnReplace) {
// given
var gateway = elementRegistry.get('ComplexGateway_1');
var newElementData = {
type: 'bpmn:ExclusiveGateway'
};
// when
var newElement = bpmnReplace.replaceElement(gateway, newElementData);
// then
var businessObject = newElement.businessObject;
expect(newElement).to.exist;
expect(is(businessObject, 'bpmn:ExclusiveGateway')).to.be.true;
expect(newElement.di.isMarkerVisible).to.be.true;
}));
it('expanded sub process', inject(function(elementRegistry, modeling, bpmnReplace, canvas) {
// given
var subProcess = elementRegistry.get('SubProcess_1'),
newElementData = {
type: 'bpmn:Transaction',
isExpanded: true
};
// when
var newElement = bpmnReplace.replaceElement(subProcess, newElementData);
// then
expect(newElement).to.exist;
expect(is(newElement.businessObject, 'bpmn:Transaction')).to.be.true;
}));
it('transaction', inject(function(elementRegistry, modeling, bpmnReplace, canvas) {
// given
var transaction = elementRegistry.get('Transaction_1'),
newElementData = {
type: 'bpmn:SubProcess',
isExpanded: true
};
// when
var newElement = bpmnReplace.replaceElement(transaction, newElementData);
// then
expect(newElement).to.exist;
expect(is(newElement.businessObject, 'bpmn:SubProcess')).to.be.true;
}));
it('event sub process', inject(function(elementRegistry, bpmnReplace) {
// given
var transaction = elementRegistry.get('SubProcess_1'),
newElementData = {
type: 'bpmn:SubProcess',
triggeredByEvent: true
};
// when
var newElement = bpmnReplace.replaceElement(transaction, newElementData);
// then
expect(newElement).to.exist;
expect(isEventSubProcess(newElement)).to.be.true;
}));
describe('boundary event', function() {
it(' with ',
inject(function(elementRegistry, modeling, bpmnReplace, canvas) {
// given
var boundaryEvent = elementRegistry.get('BoundaryEvent_1'),
boundaryBo = boundaryEvent.businessObject,
newElementData = {
type: 'bpmn:BoundaryEvent',
eventDefinitionType: 'bpmn:TimerEventDefinition',
cancelActivity: true
};
var eventDefinitions = boundaryBo.get('eventDefinitions').slice();
// when
var newElement = bpmnReplace.replaceElement(boundaryEvent, newElementData);
var newBo = newElement.businessObject;
// then
expect(newElement).to.exist;
expect(is(newBo, 'bpmn:BoundaryEvent')).to.be.true;
expect(newBo.get('eventDefinitions')).to.jsonEqual(eventDefinitions, skipId);
expect(newBo.cancelActivity).to.be.true;
})
);
it(' with ',
inject(function(elementRegistry, modeling, bpmnReplace, canvas) {
// given
var boundaryEvent = elementRegistry.get('BoundaryEvent_2'),
boundaryBo = boundaryEvent.businessObject,
newElementData = {
type: 'bpmn:BoundaryEvent',
eventDefinitionType: 'bpmn:ConditionalEventDefinition',
cancelActivity: false
};
var eventDefinitions = boundaryBo.get('eventDefinitions').slice();
// when
var newElement = bpmnReplace.replaceElement(boundaryEvent, newElementData);
var newBo = newElement.businessObject;
// then
expect(newElement).to.exist;
expect(is(newBo, 'bpmn:BoundaryEvent')).to.be.true;
expect(newBo.get('eventDefinitions')).to.jsonEqual(eventDefinitions, skipId);
expect(newBo.cancelActivity).to.be.false;
})
);
it(' with ',
inject(function(elementRegistry, modeling, bpmnReplace, canvas) {
// given
var boundaryEvent = elementRegistry.get('BoundaryEvent_1'),
newElementData = {
type: 'bpmn:BoundaryEvent',
eventDefinitionType: 'bpmn:SignalEventDefinition',
cancelActivity: false
};
// when
var newElement = bpmnReplace.replaceElement(boundaryEvent, newElementData);
var newBo = newElement.businessObject;
var newEventDefinitions = newBo.get('eventDefinitions');
var newEventDefinition = newEventDefinitions[0];
// then
expect(newElement).to.exist;
expect(newEventDefinitions).to.have.length(1);
expect(is(newBo, 'bpmn:BoundaryEvent')).to.be.true;
expect(is(newEventDefinition, 'bpmn:SignalEventDefinition')).to.be.true;
expect(newBo.cancelActivity).to.be.false;
})
);
it(' with ',
inject(function(elementRegistry, modeling, bpmnReplace, canvas) {
// given
var boundaryEvent = elementRegistry.get('BoundaryEvent_2'),
newElementData = {
type: 'bpmn:BoundaryEvent',
eventDefinitionType: 'bpmn:TimerEventDefinition'
};
// when
var newElement = bpmnReplace.replaceElement(boundaryEvent, newElementData);
var newBo = newElement.businessObject;
var newEventDefinitions = newBo.get('eventDefinitions');
var newEventDefinition = newEventDefinitions[0];
// then
expect(newElement).to.exist;
expect(newEventDefinitions).to.have.length(1);
expect(is(newBo, 'bpmn:BoundaryEvent')).to.be.true;
expect(is(newEventDefinition, 'bpmn:TimerEventDefinition')).to.be.true;
expect(newBo.cancelActivity).to.be.true;
})
);
it('updating host',
inject(function(elementRegistry, modeling, bpmnReplace, canvas) {
// given
var boundaryEvent = elementRegistry.get('BoundaryEvent_1'),
host = elementRegistry.get('Task_1'),
newElementData = {
type: 'bpmn:BoundaryEvent',
eventDefinitionType: 'bpmn:ErrorEventDefinition'
};
// when
var newElement = bpmnReplace.replaceElement(boundaryEvent, newElementData);
// then
expect(newElement.host).to.exist;
expect(newElement.host).to.eql(host);
})
);
});
});
describe('should replace in sub-process (collapsed)', function() {
var diagramXML = require('./BpmnReplace.collapsedSubProcess.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules,
moddleExtensions: {
camunda: camundaPackage
}
}));
beforeEach(inject(function(canvas) {
canvas.setRootElement(canvas.findRoot('SubProcess_Collapsed_plane'));
}));
it('task', inject(function(elementRegistry, bpmnReplace) {
// given
var task = elementRegistry.get('UserTask');
var newElementData = {
type: 'bpmn:ServiceTask'
};
// when
var newElement = bpmnReplace.replaceElement(task, newElementData);
// then
var businessObject = newElement.businessObject;
expect(newElement).to.exist;
expect(is(businessObject, 'bpmn:ServiceTask')).to.be.true;
}));
it('task with collapsed sub-process', inject(function(elementRegistry, bpmnReplace) {
// given
var task = elementRegistry.get('UserTask');
var newElementData = {
type: 'bpmn:SubProcess'
};
// when
var newElement = bpmnReplace.replaceElement(task, newElementData);
// then
var businessObject = newElement.businessObject;
expect(newElement).to.exist;
expect(is(businessObject, 'bpmn:SubProcess')).to.be.true;
}));
it('collapsed sub-process with task', inject(function(elementRegistry, bpmnReplace) {
// given
var task = elementRegistry.get('NestedCollapsed_SubProcess');
var newElementData = {
type: 'bpmn:Task'
};
// when
var newElement = bpmnReplace.replaceElement(task, newElementData);
// then
var businessObject = newElement.businessObject;
expect(newElement).to.exist;
expect(is(businessObject, 'bpmn:Task')).to.be.true;
}));
});
describe('should replace in collaboration', function() {
var diagramXML = require('./BpmnReplace.collaboration.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules,
moddleExtensions: {
camunda: camundaPackage
}
}));
it('expanded with collapsed pool', inject(function(elementRegistry, bpmnReplace) {
// given
var shape = elementRegistry.get('Participant_1');
var messageFlow = elementRegistry.get('MessageFlow_B_to_A');
var collapsedBounds = assign({}, getBounds(shape), { height: 60 });
// when
var newShape = bpmnReplace.replaceElement(shape, {
type: 'bpmn:Participant',
isExpanded: false
});
// then
expect(isExpanded(newShape)).to.be.false; // collapsed
expect(newShape.children).to.be.empty;
expect(newShape.di.isHorizontal).to.be.true;
expect(newShape).to.have.bounds(collapsedBounds);
expect(messageFlow).to.have.waypoints([
{ x: 368, y: 436 },
{ x: 368, y: newShape.y + collapsedBounds.height }
]);
}));
it('collapsed with expanded pool', inject(function(elementRegistry, bpmnReplace) {
// given
var shape = elementRegistry.get('Participant_2');
var expandedBounds = assign({}, getBounds(shape), { height: 250 });
// when
var newShape = bpmnReplace.replaceElement(shape, {
type: 'bpmn:Participant',
isExpanded: true
});
// then
expect(isExpanded(newShape)).to.be.true; // expanded
expect(newShape.children).to.be.empty;
expect(newShape.di.isHorizontal).to.be.true;
expect(newShape).to.have.bounds(expandedBounds);
}));
});
describe('should replace in vertical collaboration', function() {
var diagramXML = require('./BpmnReplace.collaboration.vertical.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules,
moddleExtensions: {
camunda: camundaPackage
}
}));
it('expanded with collapsed pool', inject(function(elementRegistry, bpmnReplace) {
// given
var shape = elementRegistry.get('V_Participant_1');
var messageFlow = elementRegistry.get('V_MessageFlow_B_to_A');
var collapsedBounds = assign({}, getBounds(shape), { width: 60 });
// when
var newShape = bpmnReplace.replaceElement(shape, {
type: 'bpmn:Participant',
isExpanded: false
});
// then
expect(isExpanded(newShape)).to.be.false; // collapsed
expect(newShape.children).to.be.empty;
expect(newShape.di.isHorizontal).to.be.false;
expect(newShape).to.have.bounds(collapsedBounds);
expect(messageFlow).to.have.waypoints([
{ x: 436, y: 368 },
{ x: newShape.x + collapsedBounds.width, y: 368 }
]);
}));
it('collapsed with expanded pool', inject(function(elementRegistry, bpmnReplace) {
// given
var shape = elementRegistry.get('V_Participant_2');
var expandedBounds = assign({}, getBounds(shape), { width: 250 });
// when
var newShape = bpmnReplace.replaceElement(shape, {
type: 'bpmn:Participant',
isExpanded: true
});
// then
expect(isExpanded(newShape)).to.be.true; // expanded
expect(newShape.children).to.be.empty;
expect(newShape.di.isHorizontal).to.be.false;
expect(newShape).to.have.bounds(expandedBounds);
}));
});
describe('should collapse pool, reconnecting message flows', function() {
var diagramXML = require('./BpmnReplace.poolMessageFlows.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules,
moddleExtensions: {
camunda: camundaPackage
}
}));
it('expanded with collapsed pool', inject(function(elementRegistry, bpmnReplace) {
// given
var shape = elementRegistry.get('Participant_1');
// when
var newShape = bpmnReplace.replaceElement(shape, {
type: 'bpmn:Participant',
isExpanded: false
});
// then
expect(isExpanded(newShape)).to.be.false; // collapsed
expect(newShape.children).to.be.empty;
expect(elementRegistry.get('MessageFlow_1')).to.exist;
expect(elementRegistry.get('MessageFlow_2')).to.exist;
}));
});
describe('should collapse vertical pool, reconnecting message flows', function() {
var diagramXML = require('./BpmnReplace.poolMessageFlows.vertical.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules,
moddleExtensions: {
camunda: camundaPackage
}
}));
it('expanded with collapsed pool', inject(function(elementRegistry, bpmnReplace) {
// given
var shape = elementRegistry.get('V_Participant_1');
// when
var newShape = bpmnReplace.replaceElement(shape, {
type: 'bpmn:Participant',
isExpanded: false
});
// then
expect(isExpanded(newShape)).to.be.false; // collapsed
expect(newShape.children).to.be.empty;
expect(elementRegistry.get('V_MessageFlow_1')).to.exist;
expect(elementRegistry.get('V_MessageFlow_2')).to.exist;
}));
});
describe('should replace with data objects', function() {
var diagramXML = require('./BpmnReplace.dataObjects.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules,
moddleExtensions: {
camunda: camundaPackage
}
}));
it('restoring dataAssociations', inject(function(elementRegistry, bpmnReplace) {
// given
var task = elementRegistry.get('Task');
// when
var serviceTask = bpmnReplace.replaceElement(task, { type: 'bpmn:ServiceTask' });
var bo = serviceTask.businessObject;
// then
// expect one incoming connection
expect(serviceTask.incoming).to.have.length(1);
var inputAssociations = bo.dataInputAssociations;
expect(inputAssociations).to.have.length(1);
var inputAssociation = inputAssociations[0];
// expect input association references __target_ref_placeholder property
expect(inputAssociation.targetRef).to.equal(bo.properties[0]);
// ...and
// expect one outgoing connection
expect(serviceTask.outgoing).to.have.length(1);
var outputAssociations = bo.dataOutputAssociations;
expect(outputAssociations).to.have.length(1);
}));
});
describe('position and size', function() {
var diagramXML = require('../../../fixtures/bpmn/features/replace/01_replace.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules,
moddleExtensions: {
camunda: camundaPackage
}
}));
it('should keep position', inject(function(elementRegistry, bpmnReplace) {
// given
var task = elementRegistry.get('Task_1');
var newElementData = {
type: 'bpmn:UserTask'
};
// when
var newElement = bpmnReplace.replaceElement(task, newElementData);
// then
expect(newElement.x).to.equal(task.x);
expect(newElement.y).to.equal(task.y);
}));
it('should keep label position', inject(function(elementRegistry, bpmnReplace, modeling) {
// given
var startEvent = elementRegistry.get('StartEvent_1');
var label = elementRegistry.get('StartEvent_1_label');
var newElementData = {
type: 'bpmn:EndEvent'
};
// when
var newElement = bpmnReplace.replaceElement(startEvent, newElementData);
// then
expect(newElement.label.x).to.equal(label.x);
expect(newElement.label.y).to.equal(label.y);
}));
it('should assign default size when replacing task with expanded sub process', inject(
function(elementRegistry, bpmnReplace) {
// given
var task = elementRegistry.get('Task_1');
var mid = getMid(task);
var newElementData = {
type: 'bpmn:SubProcess',
isExpanded: true
};
// when
var newElement = bpmnReplace.replaceElement(task, newElementData);
// then
expect(newElement).to.exist;
expect(is(newElement, 'bpmn:SubProcess')).to.be.true;
expect(getMid(newElement)).to.eql(mid);
expect(newElement.width).to.equal(350);
expect(newElement.height).to.equal(200);
}
));
});
describe('selection', function() {
var diagramXML = require('../../../fixtures/bpmn/features/replace/01_replace.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules,
moddleExtensions: {
camunda: camundaPackage
}
}));
it('should select after replace',
inject(function(elementRegistry, selection, bpmnReplace) {
// given
var task = elementRegistry.get('Task_1');
var newElementData = {
type: 'bpmn:UserTask'
};
// when
var newElement = bpmnReplace.replaceElement(task, newElementData);
// then
expect(selection.get()).to.include(newElement);
})
);
});
describe('label', function() {
var diagramXML = require('../../../fixtures/bpmn/features/replace/01_replace.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules,
moddleExtensions: {
camunda: camundaPackage
}
}));
it('should keep internal labels',
inject(function(elementRegistry, bpmnReplace) {
// given
var task = elementRegistry.get('Task_1');
var newElementData = {
type: 'bpmn:UserTask'
};
// when
var newElement = bpmnReplace.replaceElement(task, newElementData);
// then
expect(newElement.businessObject.name).to.equal('Task Caption');
})
);
it('should keep external labels',
inject(function(elementRegistry, bpmnReplace) {
// given
var startEvent = elementRegistry.get('StartEvent_1');
var newElementData = {
type: 'bpmn:EndEvent'
};
// when
var newElement = bpmnReplace.replaceElement(startEvent, newElementData);
// then
expect(newElement.label.labelTarget).to.equal(newElement);
expect(newElement.businessObject.name).to.equal('KEEP ME');
})
);
});
describe('undo support', function() {
var diagramXML = require('../../../fixtures/bpmn/features/replace/01_replace.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules,
moddleExtensions: {
camunda: camundaPackage
}
}));
it('should undo', inject(function(elementRegistry, bpmnReplace, commandStack) {
// given
var task = elementRegistry.get('Task_1');
var newElementData = {
type: 'bpmn:UserTask'
};
bpmnReplace.replaceElement(task, newElementData);
// when
commandStack.undo();
// then
var target = elementRegistry.get('Task_1'),
businessObject = target.businessObject;
expect(target).to.exist;
expect(is(businessObject, 'bpmn:Task')).to.be.true;
}));
it('should redo', inject(function(elementRegistry, bpmnReplace, commandStack) {
// given
var task = elementRegistry.get('Task_1');
var newElementData = {
type: 'bpmn:UserTask'
};
var newElementData2 = {
type: 'bpmn:ServiceTask'
};
var usertask = bpmnReplace.replaceElement(task, newElementData);
var servicetask = bpmnReplace.replaceElement(usertask, newElementData2);
commandStack.undo();
commandStack.undo();
// when
commandStack.redo();
commandStack.redo();
// then
var businessObject = servicetask.businessObject;
expect(servicetask).to.exist;
expect(is(businessObject, 'bpmn:ServiceTask')).to.be.true;
}));
});
describe('connection handling', function() {
var diagramXML = require('../../../fixtures/bpmn/features/replace/01_replace.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules,
moddleExtensions: {
camunda: camundaPackage
}
}));
it('should reconnect valid', inject(function(elementRegistry, bpmnReplace) {
// given
var task = elementRegistry.get('Task_1');
var newElementData = {
type: 'bpmn:UserTask'
};
// when
var newElement = bpmnReplace.replaceElement(task, newElementData);
// then
var incoming = newElement.incoming[0],
outgoing = newElement.outgoing[0],
source = incoming.source,
target = outgoing.target;
expect(incoming).to.exist;
expect(outgoing).to.exist;
expect(source).to.eql(elementRegistry.get('StartEvent_1'));
expect(target).to.eql(elementRegistry.get('ExclusiveGateway_1'));
}));
it('should remove invalid (incoming)', inject(function(elementRegistry, bpmnReplace) {
// given
var task = elementRegistry.get('StartEvent_1');
var newElementData = {
type: 'bpmn:EndEvent'
};
// when
var newElement = bpmnReplace.replaceElement(task, newElementData);
// then
expect(newElement.incoming).to.be.empty;
}));
it('should remove invalid (outgoing)', inject(function(elementRegistry, bpmnReplace) {
// given
var task = elementRegistry.get('EndEvent_1');
var newElementData = {
type: 'bpmn:StartEvent'
};
// when
var newElement = bpmnReplace.replaceElement(task, newElementData);
// then
expect(newElement.outgoing).to.be.empty;
}));
describe('undo support', function() {
it('should reconnect valid', inject(function(elementRegistry, bpmnReplace, commandStack) {
// given
var task = elementRegistry.get('Task_1');
var newElementData = {
type: 'bpmn:UserTask'
};
bpmnReplace.replaceElement(task, newElementData);
// when
commandStack.undo();
// then
var newTask = elementRegistry.get('Task_1');
var incoming = newTask.incoming[0],
outgoing = newTask.outgoing[0],
source = incoming.source,
target = outgoing.target;
expect(incoming).to.exist;
expect(outgoing).to.exist;
expect(source).to.eql(elementRegistry.get('StartEvent_1'));
expect(target).to.eql(elementRegistry.get('ExclusiveGateway_1'));
}));
it('should remove invalid (invalid)',
inject(function(elementRegistry, bpmnReplace, commandStack) {
// given
var startEvent = elementRegistry.get('StartEvent_1');
var newElementData = {
type: 'bpmn:EndEvent'
};
bpmnReplace.replaceElement(startEvent, newElementData);
// when
commandStack.undo();
// then
var newEvent = elementRegistry.get('StartEvent_1');
var incoming = newEvent.incoming[0],
outgoing = newEvent.outgoing[0],
target = outgoing.target;
expect(incoming).not.to.exist;
expect(outgoing).to.exist;
expect(target).to.eql(elementRegistry.get('Task_1'));
})
);
it('should remove invalid outgoing connections',
inject(function(elementRegistry, bpmnReplace, commandStack) {
// given
var endEvent = elementRegistry.get('EndEvent_1');
var newElementData = {
type: 'bpmn:StartEvent'
};
bpmnReplace.replaceElement(endEvent, newElementData);
// when
commandStack.undo();
// then
var newEvent = elementRegistry.get('EndEvent_1');
var incoming = newEvent.incoming[0],
outgoing = newEvent.outgoing[0],
source = incoming.source;
expect(incoming).to.exist;
expect(outgoing).not.to.exist;
expect(source).to.eql(elementRegistry.get('Transaction_1'));
})
);
});
describe('redo support', function() {
it('should reconnect valid connections',
inject(function(elementRegistry, bpmnReplace, commandStack) {
// given
var task = elementRegistry.get('Task_1');
var newElementData = {
type: 'bpmn:UserTask'
};
var newElement = bpmnReplace.replaceElement(task, newElementData);
// when
commandStack.undo();
commandStack.redo();
// then
var incoming = newElement.incoming[0],
outgoing = newElement.outgoing[0],
source = incoming.source,
target = outgoing.target;
expect(incoming).to.exist;
expect(outgoing).to.exist;
expect(source).to.eql(elementRegistry.get('StartEvent_1'));
expect(target).to.eql(elementRegistry.get('ExclusiveGateway_1'));
})
);
it('should remove invalid incoming connections',
inject(function(elementRegistry, bpmnReplace, commandStack) {
// given
var startEvent = elementRegistry.get('StartEvent_1');
var newElementData = {
type: 'bpmn:EndEvent'
};
var newElement = bpmnReplace.replaceElement(startEvent, newElementData);
// when
commandStack.undo();
commandStack.redo();
// then
var incoming = newElement.incoming[0],
outgoing = newElement.outgoing[0];
expect(incoming).not.to.exist;
expect(outgoing).not.to.exist;
})
);
it('should remove invalid outgoing connections',
inject(function(elementRegistry, bpmnReplace, commandStack) {
// given
var endEvent = elementRegistry.get('EndEvent_1');
var newElementData = {
type: 'bpmn:StartEvent'
};
var newElement = bpmnReplace.replaceElement(endEvent, newElementData);
// when
commandStack.undo();
commandStack.redo();
// then
var incoming = newElement.incoming[0],
outgoing = newElement.outgoing[0];
expect(incoming).not.to.exist;
expect(outgoing).not.to.exist;
})
);
});
});
describe('children handling', function() {
var diagramXML = require('../../../fixtures/bpmn/features/replace/01_replace.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules,
moddleExtensions: {
camunda: camundaPackage
}
}));
it('should update bpmn containment properly', inject(function(elementRegistry, modeling, bpmnReplace) {
// given
var subProcessShape = elementRegistry.get('SubProcess_1');
var startEventShape = elementRegistry.get('StartEvent_2');
var taskShape = elementRegistry.get('Task_2');
var sequenceFlowConnection = elementRegistry.get('SequenceFlow_4');
var transactionShapeData = {
type: 'bpmn:Transaction'
};
// when
var transactionShape = bpmnReplace.replaceElement(subProcessShape, transactionShapeData);
// then
var subProcess = subProcessShape.businessObject,
transaction = transactionShape.businessObject;
var transactionChildren = transaction.get('flowElements');
var subProcessChildren = subProcess.get('flowElements');
expect(transactionChildren).to.include(startEventShape.businessObject);
expect(transactionChildren).to.include(taskShape.businessObject);
expect(transactionChildren).to.include(sequenceFlowConnection.businessObject);
expect(subProcessChildren).not.to.include(startEventShape.businessObject);
expect(subProcessChildren).not.to.include(taskShape.businessObject);
expect(subProcessChildren).not.to.include(sequenceFlowConnection.businessObject);
}));
});
describe('sub processes', function() {
var diagramXML = require('../../../fixtures/bpmn/features/replace/01_replace.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules,
moddleExtensions: {
camunda: camundaPackage
}
}));
it('should allow morphing expanded into expanded ad hoc',
inject(function(bpmnReplace, elementRegistry) {
// given
var element = elementRegistry.get('SubProcess_1');
var newElementData = {
type: 'bpmn:AdHocSubProcess'
};
// when
var newElement = bpmnReplace.replaceElement(element, newElementData);
// then
expect(is(newElement, 'bpmn:AdHocSubProcess')).to.be.true;
expect(isExpanded(newElement)).to.be.true;
})
);
it('should allow morphing expanded ad hoc into expanded',
inject(function(bpmnReplace, elementRegistry) {
// given
var element = elementRegistry.get('AdHocSubProcessExpanded');
var newElementData = {
type: 'bpmn:SubProcess'
};
// when
var newElement = bpmnReplace.replaceElement(element, newElementData);
// then
expect(is(newElement, 'bpmn:SubProcess')).to.be.true;
expect(is(newElement, 'bpmn:AdHocSubProcess')).to.be.false;
expect(isExpanded(newElement)).to.be.true;
})
);
it('should allow morphing collapsed into collapsed ad hoc',
inject(function(bpmnReplace, elementRegistry) {
// given
var element = elementRegistry.get('SubProcessCollapsed');
var newElementData = {
type: 'bpmn:AdHocSubProcess'
};
// when
var newElement = bpmnReplace.replaceElement(element, newElementData);
// then
expect(is(newElement, 'bpmn:AdHocSubProcess')).to.be.true;
expect(isExpanded(newElement)).not.to.be.true;
})
);
it('should allow morphing collapsed ad hoc into collapsed',
inject(function(bpmnReplace, elementRegistry) {
// given
var element = elementRegistry.get('AdHocSubProcessCollapsed');
var newElementData = {
type: 'bpmn:SubProcess'
};
// when
var newElement = bpmnReplace.replaceElement(element, newElementData);
// then
expect(is(newElement, 'bpmn:SubProcess')).to.be.true;
expect(is(newElement, 'bpmn:AdHocSubProcess')).to.be.false;
expect(isExpanded(newElement)).not.to.be.true;
})
);
it('should allow expanding newly created subprocess',
inject(function(bpmnReplace, elementFactory) {
// given
var collapsedProcess = elementFactory.createShape({
type: 'bpmn:SubProcess',
isExpanded: false
});
var newElementData = {
type: 'bpmn:SubProcess',
isExpanded: true
};
// when
var newElement = bpmnReplace.replaceElement(collapsedProcess, newElementData);
// then
expect(is(newElement, 'bpmn:SubProcess')).to.be.true;
expect(isExpanded(newElement)).to.be.true;
})
);
it('should keep size when morphing ad hoc',
inject(function(bpmnReplace, elementRegistry, modeling) {
// given
var element = elementRegistry.get('SubProcess_1');
var newElementData = {
type: 'bpmn:AdHocSubProcess'
};
var width = element.width,
height = element.height;
modeling.resizeShape(element, {
x: element.x,
y: element.y,
width: width + 20,
height: height + 20
});
// when
var newElement = bpmnReplace.replaceElement(element, newElementData);
// then
expect(is(newElement, 'bpmn:AdHocSubProcess')).to.be.true;
expect(isExpanded(newElement)).to.be.true;
expect(newElement.width).to.equal(width + 20);
expect(newElement.height).to.equal(height + 20);
})
);
it('should remove children of collapsed sub process not morphing into expanded',
inject(function(bpmnReplace, elementRegistry, modeling) {
// given
var element = elementRegistry.get('SubProcess_1');
var newElementData = {
type: 'bpmn:CallActivity'
};
modeling.toggleCollapse(element);
// when
var newElement = bpmnReplace.replaceElement(element, newElementData);
// then
expect(is(newElement, 'bpmn:CallActivity')).to.be.true;
}));
it('should drop event type from start event after moving it into sub process',
inject(function(bpmnReplace, elementRegistry, modeling) {
// given
var startEvent = elementRegistry.get('StartEvent_4'),
subProcess = elementRegistry.get('SubProcess_2');
// when
modeling.moveElements([ startEvent ], { x: 100, y: 0 }, subProcess);
var startEventAfter = elementRegistry.filter(function(element) {
return is(element, 'bpmn:StartEvent') && element.parent === subProcess;
})[0];
// then
expect(startEventAfter.businessObject.get('eventDefinitions')).is.empty;
})
);
it('should not drop event type from start event after moving it into event sub process',
inject(function(bpmnReplace, elementRegistry, modeling) {
// given
var startEvent = elementRegistry.get('StartEvent_5'),
subProcess = elementRegistry.get('EventSubProcess_2');
// when
modeling.moveElements([ startEvent ], { x: -100, y: 0 }, subProcess);
var startEventAfter = elementRegistry.filter(function(element) {
return is(element, 'bpmn:StartEvent') && element.parent === subProcess;
})[0];
// then
expect(startEventAfter.businessObject.get('eventDefinitions')[0].$type).to.equal('bpmn:MessageEventDefinition');
})
);
});
describe('morph task with boundaryEvent', function() {
var diagramXML = require('../../../fixtures/bpmn/features/replace/01_replace.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules,
moddleExtensions: {
camunda: camundaPackage
}
}));
it('to expanded sub process', inject(function(bpmnReplace, elementRegistry) {
// given
var element = elementRegistry.get('Task_1');
var newElementData = {
type: 'bpmn:SubProcess',
isExpanded: true
};
// when
var newElement = bpmnReplace.replaceElement(element, newElementData);
// then
expect(is(newElement, 'bpmn:SubProcess')).to.be.true;
expect(isExpanded(newElement)).to.be.true;
// and keep boundaryEvent
expect(newElement.attachers.length).to.be.equal(2);
}));
it('to collapsed sub process', inject(function(bpmnReplace, elementRegistry) {
// given
var element = elementRegistry.get('Task_1');
var newElementData = {
type: 'bpmn:SubProcess',
isExpanded: false
};
// when
var newElement = bpmnReplace.replaceElement(element, newElementData);
// then
expect(is(newElement, 'bpmn:SubProcess')).to.be.true;
expect(isExpanded(newElement)).to.be.false;
// and keep boundaryEvent
expect(newElement.attachers.length).to.eql(2);
}));
});
describe('compensation activity', function() {
var diagramXML = require('./BpmnReplace.compensation.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules,
moddleExtensions: {
camunda: camundaPackage
}
}));
it('should keep isForCompensation attr', inject(function(elementRegistry, bpmnReplace) {
// given
var task = elementRegistry.get('Task_1');
var newElementData = {
type: 'bpmn:ServiceTask'
};
// when
var newElement = bpmnReplace.replaceElement(task, newElementData);
// then
expect(newElement.businessObject.isForCompensation).to.be.true;
}));
});
describe('event sub processes', function() {
var diagramXML = require('./BpmnReplace.eventSubProcesses.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules,
moddleExtensions: {
camunda: camundaPackage
}
}));
it('should remove connections',
inject(function(elementRegistry, bpmnReplace) {
// given
var transaction = elementRegistry.get('SubProcess_1');
var newElementData = {
type: 'bpmn:SubProcess',
triggeredByEvent: true
};
// when
var newElement = bpmnReplace.replaceElement(transaction, newElementData);
// then
var incoming = newElement.incoming[0],
outgoing = newElement.outgoing[0];
expect(incoming).not.to.exist;
expect(outgoing).not.to.exist;
})
);
it('should replace non-interrupting start event after moving it outside event sub process',
inject(function(bpmnReplace, elementRegistry, modeling) {
// given
var startEvent = elementRegistry.get('StartEvent_2'),
root = elementRegistry.get('Process_1');
// when
modeling.moveElements([ startEvent ], { x: 0, y: 200 }, root);
var startEventAfter = elementRegistry.filter(function(element) {
return is(element, 'bpmn:StartEvent') && element.parent === root;
})[0];
// then
expect(isInterrupting(startEventAfter)).to.be.true;
expect(startEventAfter.parent).to.equal(root);
})
);
it('should replace non-interrupting start event after moving it to a regular sub process',
inject(function(bpmnReplace, elementRegistry, modeling) {
// given
var startEvent = elementRegistry.get('StartEvent_2'),
subProcess = elementRegistry.get('SubProcess_1');
// when
modeling.moveElements([ startEvent ], { x: 260, y: 60 }, subProcess);
var startEventAfter = elementRegistry.filter(function(element) {
return is(element, 'bpmn:StartEvent') && element.parent === subProcess;
})[0];
// then
expect(isInterrupting(startEventAfter)).to.be.true;
expect(startEventAfter.parent).to.equal(subProcess);
})
);
it('should not replace non-interrupting start event after moving it to another event sub process',
inject(function(bpmnReplace, elementRegistry, modeling) {
// given
var startEvent = elementRegistry.get('StartEvent_2'),
subProcess = elementRegistry.get('SubProcess_1');
var eventSubProcess = bpmnReplace.replaceElement(subProcess, {
type: 'bpmn:SubProcess',
triggeredByEvent: true,
isExpanded: true
});
// when
modeling.moveElements([ startEvent ], { x: 260, y: 60 }, eventSubProcess);
var startEventAfter = elementRegistry.filter(function(element) {
return is(element, 'bpmn:StartEvent') && element.parent === eventSubProcess && element.type !== 'label';
})[1];
// then
expect(startEvent.id).to.equal(startEventAfter.id);
expect(startEventAfter.parent).to.equal(eventSubProcess);
})
);
it('should not replace interrupting start event after moving it outside event sub process',
inject(function(bpmnReplace, elementRegistry, modeling) {
// given
var startEvent = elementRegistry.get('StartEvent_2'),
root = elementRegistry.get('Process_1');
var interruptingStartEvent = bpmnReplace.replaceElement(startEvent, { type: 'bpmn:StartEvent' });
// when
modeling.moveElements([ interruptingStartEvent ], { x: 0, y: 200 }, root);
var startEventAfter = elementRegistry.filter(function(element) {
return is(element, 'bpmn:StartEvent')
&& element.type !== 'label'
&& element.parent === root;
})[0];
// then
expect(startEventAfter).to.equal(interruptingStartEvent);
expect(startEventAfter.parent).to.equal(root);
})
);
it('should replace non-interrupting start event when replacing parent event sub process',
inject(function(elementRegistry, bpmnReplace) {
// given
var eventSubProcess = elementRegistry.get('SubProcess_2');
// when
var subProcess = bpmnReplace.replaceElement(eventSubProcess, { type: 'bpmn:SubProcess' });
// then
var replacedStartEvent = elementRegistry.filter(function(element) {
return (element.parent === subProcess && element.type !== 'label');
})[0];
expect(isInterrupting(replacedStartEvent)).to.be.true;
expect(replacedStartEvent.parent).to.equal(subProcess);
})
);
it('should not replace non-interrupting start event when moving parent event sub process',
inject(function(elementRegistry, bpmnReplace, modeling) {
// given
var eventSubProcess = elementRegistry.get('SubProcess_2'),
startEvent = elementRegistry.get('StartEvent_2');
// when
modeling.moveElements([ eventSubProcess ], { x: 20, y: 30 });
// start event after moving parent
var startEventAfter = elementRegistry.filter(function(element) {
return (element.parent === eventSubProcess && element.type !== 'label');
})[0];
// then
expect(startEventAfter).to.equal(startEvent);
expect(startEventAfter.parent).to.eql(eventSubProcess);
})
);
it('should replace error start event after moving it outside event sub process',
inject(function(elementRegistry, bpmnReplace, modeling) {
// given
var startEvent = elementRegistry.get('StartEvent_3'),
root = elementRegistry.get('Process_1');
// when
modeling.moveElements([ startEvent ], { x: 0, y: 200 }, root);
var startEventAfter = elementRegistry.filter(function(element) {
return is(element, 'bpmn:StartEvent') && element.parent === root;
})[0];
// then
expect(hasErrorEventDefinition(startEventAfter)).to.be.false;
expect(startEventAfter.parent).to.equal(root);
})
);
it('should replace error start event after moving it to a regular sub process',
inject(function(bpmnReplace, elementRegistry, modeling) {
// given
var startEvent = elementRegistry.get('StartEvent_3'),
subProcess = elementRegistry.get('SubProcess_1');
// when
modeling.moveElements([ startEvent ], { x: 260, y: 60 }, subProcess);
var startEventAfter = elementRegistry.filter(function(element) {
return is(element, 'bpmn:StartEvent') && element.parent === subProcess;
})[0];
// then
expect(hasErrorEventDefinition(startEventAfter)).to.be.false;
expect(startEventAfter.parent).to.equal(subProcess);
})
);
it('should not replace error start event after moving it to another event sub process',
inject(function(bpmnReplace, elementRegistry, modeling) {
// given
var startEvent = elementRegistry.get('StartEvent_3'),
subProcess = elementRegistry.get('SubProcess_1');
var eventSubProcess = bpmnReplace.replaceElement(subProcess, {
type: 'bpmn:SubProcess',
triggeredByEvent: true,
isExpanded: true
});
// when
modeling.moveElements([ startEvent ], { x: 260, y: 60 }, eventSubProcess);
var startEventAfter = elementRegistry.filter(function(element) {
return is(element, 'bpmn:StartEvent') && element.parent === eventSubProcess && element.type !== 'label';
})[1];
// then
expect(hasErrorEventDefinition(startEventAfter)).to.be.true;
expect(startEventAfter.parent).to.equal(eventSubProcess);
})
);
it('should replace error start event when replacing parent event sub process',
inject(function(elementRegistry, bpmnReplace) {
// given
var eventSubProcess = elementRegistry.get('SubProcess_3');
// when
var subProcess = bpmnReplace.replaceElement(eventSubProcess, { type: 'bpmn:SubProcess' });
// then
var replacedStartEvent = elementRegistry.filter(function(element) {
return (element.parent === subProcess && element.type !== 'label');
})[0];
expect(hasErrorEventDefinition(replacedStartEvent)).to.be.false;
expect(replacedStartEvent.parent).to.equal(subProcess);
})
);
it('should not replace error start event when moving parent event sub process',
inject(function(elementRegistry, bpmnReplace, modeling) {
// given
var eventSubProcess = elementRegistry.get('SubProcess_3'),
startEvent = elementRegistry.get('StartEvent_3');
// when
modeling.moveElements([ eventSubProcess ], { x: 20, y: 30 });
// start event after moving parent
var startEventAfter = elementRegistry.filter(function(element) {
return (element.parent === eventSubProcess && element.type !== 'label');
})[0];
// then
expect(startEventAfter).to.equal(startEvent);
expect(startEventAfter.parent).to.eql(eventSubProcess);
})
);
it('should remove `isForCompensation` when replacing sub process', inject(function(elementRegistry, bpmnReplace) {
// given
var compensationSubProcess = elementRegistry.get('SubProcess_4');
// when
var subProcess = bpmnReplace.replaceElement(
compensationSubProcess,
{ type: 'bpmn:SubProcess', triggeredByEvent: true }
);
// then
expect(subProcess.businessObject.isForCompensation).to.be.false;
}));
});
describe('events', function() {
var diagramXML = require('../../../fixtures/bpmn/basic.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules,
moddleExtensions: {
camunda: camundaPackage
}
}));
it('should properly set parent of event definitions', inject(
function(elementRegistry, bpmnReplace) {
// given
var startEvent = elementRegistry.get('StartEvent_1');
var messageEvent = bpmnReplace.replaceElement(startEvent, {
type: 'bpmn:StartEvent',
eventDefinitionType: 'bpmn:MessageEventDefinition'
});
var parent = messageEvent.businessObject.get('eventDefinitions')[0].$parent;
expect(parent).to.exist;
expect(parent).to.equal(messageEvent.businessObject);
})
);
it('should add condition with ConditionalEventDefinition', inject(
function(elementRegistry, bpmnReplace) {
// given
var startEvent = elementRegistry.get('StartEvent_1');
// when
var messageEvent = bpmnReplace.replaceElement(startEvent, {
type: 'bpmn:StartEvent',
eventDefinitionType: 'bpmn:ConditionalEventDefinition'
});
var definition = messageEvent.businessObject.get('eventDefinitions')[0];
// then
expect(definition.condition).to.exist;
})
);
it('should set host for boundary event if provided',
inject(function(elementRegistry, bpmnReplace) {
// given
var startEvent = elementRegistry.get('StartEvent_1'),
task = elementRegistry.get('Task_1');
// when
var boundaryEvent = bpmnReplace.replaceElement(startEvent, {
type: 'bpmn:BoundaryEvent',
host: task
});
// then
expect(boundaryEvent).to.exist;
expect(boundaryEvent).to.have.property('host', task);
expect(task).to.have.property('attachers');
expect(task.attachers).to.deep.eql([ boundaryEvent ]);
})
);
});
describe('properties', function() {
var copyPropertiesXML = require('../../../fixtures/bpmn/features/replace/copy-properties.bpmn');
beforeEach(bootstrapModeler(copyPropertiesXML, {
modules: testModules,
moddleExtensions: {
camunda: camundaPackage
}
}));
it('should copy properties', inject(function(bpmnReplace, elementRegistry) {
// given
var task = elementRegistry.get('Task_1');
var newElementData = {
type: 'bpmn:ServiceTask'
};
// when
var newElement = bpmnReplace.replaceElement(task, newElementData);
// then
var businessObject = newElement.businessObject;
expect(businessObject.asyncBefore).to.be.true;
expect(businessObject.jobPriority).to.equal('100');
var documentation = businessObject.documentation;
expect(documentation).to.have.length(1);
expect(documentation[0]).to.exist;
expect(documentation[0].text).to.equal('hello world');
var extensionElements = businessObject.extensionElements;
expect(extensionElements.values).to.have.length(4);
var inputOutput = extensionElements.values[0],
properties = extensionElements.values[1],
executionListener = extensionElements.values[2],
retryCycle = extensionElements.values[3];
expect(is(inputOutput, 'camunda:InputOutput')).to.be.true;
expect(is(inputOutput.inputParameters[0], 'camunda:InputParameter')).to.be.true;
expect(inputOutput.inputParameters[0].name).to.equal('Input_1');
expect(inputOutput.inputParameters[0].value).to.equal('foo');
expect(is(inputOutput.outputParameters[0], 'camunda:OutputParameter')).to.be.true;
expect(inputOutput.outputParameters[0].name).to.equal('Output_1');
expect(inputOutput.outputParameters[0].value).to.equal('bar');
expect(is(properties, 'camunda:Properties')).to.be.true;
expect(is(properties.values[0], 'camunda:Property')).to.be.true;
expect(properties.values[0].name).to.equal('bar');
expect(properties.values[0].value).to.equal('foo');
expect(is(executionListener, 'camunda:ExecutionListener')).to.be.true;
expect(executionListener.class).to.equal('reallyClassy');
expect(executionListener.event).to.equal('start');
expect(is(retryCycle, 'camunda:FailedJobRetryTimeCycle')).to.be.true;
expect(retryCycle.body).to.equal('10');
}));
});
describe('colors', function() {
var diagramXML = require('../../../fixtures/bpmn/features/replace/01_replace.bpmn');
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
it('should have new di', inject(function(elementRegistry, bpmnReplace) {
// given
var task = elementRegistry.get('Task_1');
var di = getDi(task);
var newElementData = {
type: 'bpmn:UserTask'
};
// when
var newElement = bpmnReplace.replaceElement(task, newElementData);
// then
var newDi = getDi(newElement);
expect(newDi).to.not.equal(di);
}));
it('should maintain colors', inject(function(elementRegistry, bpmnReplace, modeling) {
// given
var task = elementRegistry.get('Task_1');
var newElementData = {
type: 'bpmn:UserTask'
},
fill = '#ff0000',
stroke = '#00ff00';
modeling.setColor(task, { fill: fill, stroke: stroke });
// when
var newElement = bpmnReplace.replaceElement(task, newElementData);
// then
var di = getDi(newElement);
expect(di.get('background-color')).to.equal(fill);
expect(di.get('border-color')).to.equal(stroke);
// TODO @barmac: remove when we drop bpmn.io properties
expect(di.fill).to.equal(fill);
expect(di.stroke).to.equal(stroke);
}));
});
describe('center', function() {
var diagramXML = require('../../../fixtures/bpmn/features/replace/data-elements.bpmn');
var testModules = [
modelingModule,
coreModule
];
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
describe('data store reference to data object reference', function() {
it('should center on replace', inject(function(bpmnReplace, elementRegistry) {
// given
var dataStoreReference = elementRegistry.get('DataStoreReference_1');
var positionX = dataStoreReference.x;
// when
bpmnReplace.replaceElement(dataStoreReference, { type: 'bpmn:DataObjectReference' });
var dataObjectReference = elementRegistry.get('DataStoreReference_1');
var newPositionX = positionX + (dataStoreReference.width - dataObjectReference.width) / 2;
// then
expect(dataObjectReference.x).to.eql(newPositionX);
}));
it('should undo', inject(function(bpmnReplace, commandStack, elementRegistry) {
// given
var dataStoreReference = elementRegistry.get('DataStoreReference_1');
var positionX = dataStoreReference.x;
bpmnReplace.replaceElement(dataStoreReference, { type: 'bpmn:DataObjectReference' });
// when
commandStack.undo();
// then
expect(elementRegistry.get('DataStoreReference_1').x).to.eql(positionX);
}));
it('should redo', inject(function(bpmnReplace, commandStack, elementRegistry) {
// given
var dataStoreReference = elementRegistry.get('DataStoreReference_1');
var positionX = dataStoreReference.x;
bpmnReplace.replaceElement(dataStoreReference, { type: 'bpmn:DataObjectReference' });
commandStack.undo();
// when
commandStack.redo();
var dataObjectReference = elementRegistry.get('DataStoreReference_1');
var newPositionX = positionX + (dataStoreReference.width - dataObjectReference.width) / 2;
// then
expect(dataObjectReference.x).to.eql(newPositionX);
}));
});
describe('data object reference to data store reference', function() {
it('should center on replace', inject(function(bpmnReplace, elementRegistry) {
// given
var dataObjectReference = elementRegistry.get('DataObjectReference_1');
var positionX = dataObjectReference.x;
// when
bpmnReplace.replaceElement(dataObjectReference, { type: 'bpmn:DataStoreReference' });
var dataStoreReference = elementRegistry.get('DataObjectReference_1');
var newPositionX = positionX + (dataObjectReference.width - dataStoreReference.width) / 2;
// then
expect(dataStoreReference.x).to.eql(newPositionX);
}));
it('should undo', inject(function(bpmnReplace, commandStack, elementRegistry) {
// given
var dataObjectReference = elementRegistry.get('DataObjectReference_1');
var positionX = dataObjectReference.x;
bpmnReplace.replaceElement(dataObjectReference, { type: 'bpmn:DataStoreReference' });
// when
commandStack.undo();
// then
expect(elementRegistry.get('DataObjectReference_1').x).to.eql(positionX);
}));
it('should redo', inject(function(bpmnReplace, commandStack, elementRegistry) {
// given
var dataObjectReference = elementRegistry.get('DataObjectReference_1');
var positionX = dataObjectReference.x;
bpmnReplace.replaceElement(dataObjectReference, { type: 'bpmn:DataStoreReference' });
commandStack.undo();
// when
commandStack.redo();
var dataStoreReference = elementRegistry.get('DataObjectReference_1');
var newPositionX = positionX + (dataObjectReference.width - dataStoreReference.width) / 2;
// then
expect(dataStoreReference.x).to.eql(newPositionX);
}));
});
});
});
// helpers ////////////////////////
function skipId(key, value) {
if (key === 'id') {
return;
}
return value;
}
function getBounds(shape) {
return pick(shape, [ 'x', 'y', 'width', 'height' ]);
}
================================================
FILE: test/spec/features/replace/ReplaceRulesSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import modelingModule from 'lib/features/modeling';
import replaceModule from 'lib/features/replace';
import coreModule from 'lib/core';
describe('features/replace - rules', function() {
var diagramXML = require('../../../fixtures/bpmn/features/replace/association-gateways.bpmn');
var testModules = [ coreModule, modelingModule, replaceModule ];
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
describe('should keep associations', function() {
it('replacing Gateway -> EventBasedGateway', inject(function(elementRegistry, modeling, bpmnReplace) {
// given
var element = elementRegistry.get('ExclusiveGateway_140v6lc');
var target = {
type: 'bpmn:EventBasedGateway'
};
// when
bpmnReplace.replaceElement(element, target);
// then
expect(elementRegistry.get('Association_0gzxvep')).to.exist;
expect(elementRegistry.get('SequenceFlow_1rme11l')).to.exist;
expect(elementRegistry.get('SequenceFlow_0608fzs')).not.to.exist;
}));
it('replacing StartEvent -> EndEvent', inject(function(elementRegistry, modeling, bpmnReplace) {
// given
var element = elementRegistry.get('StartEvent_1');
var target = {
type: 'bpmn:EndEvent'
};
// when
bpmnReplace.replaceElement(element, target);
// then
expect(elementRegistry.get('Association_1ncsghq')).to.exist;
expect(elementRegistry.get('SequenceFlow_0fn1a6r')).not.to.exist;
}));
it('replacing EndEvent -> StartEvent', inject(function(elementRegistry, modeling, bpmnReplace) {
// given
var element = elementRegistry.get('EndEvent_1');
var target = {
type: 'bpmn:StartEvent'
};
// when
bpmnReplace.replaceElement(element, target);
// then
expect(elementRegistry.get('Association_06tpzma')).to.exist;
expect(elementRegistry.get('SequenceFlow_19u6x8u')).not.to.exist;
}));
});
});
================================================
FILE: test/spec/features/replace/SubProcess-collapsed.bpmn
================================================
SequenceFlow_1
SequenceFlow_1
================================================
FILE: test/spec/features/replace-preview/BpmnReplacePreview.bpmn
================================================
================================================
FILE: test/spec/features/replace-preview/BpmnReplacePreviewSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import replacePreviewModule from 'lib/features/replace-preview';
import moveModule from 'diagram-js/lib/features/move';
import modelingModule from 'lib/features/modeling';
import coreModule from 'lib/core';
import {
createCanvasEvent as canvasEvent
} from '../../../util/MockEvents';
import {
assign
} from 'min-dash';
import {
attr as svgAttr,
clone as svgClone,
innerSVG
} from 'tiny-svg';
describe('features/replace-preview', function() {
var diagramXML = require('./BpmnReplacePreview.bpmn');
var startEvent1,
rootElement;
var getGfx,
moveShape;
beforeEach(bootstrapModeler(diagramXML, {
modules: [
replacePreviewModule,
moveModule,
modelingModule,
coreModule
]
}));
beforeEach(inject(function(canvas, elementRegistry, elementFactory, move, dragging) {
startEvent1 = elementRegistry.get('StartEvent.1');
rootElement = canvas.getRootElement();
/**
* returns the gfx representation of an element type
*
* @param {Object} elementData
*
* @return {Object}
*/
getGfx = function(elementData) {
assign(elementData, { x: 0, y: 0 });
var tempShape = elementFactory.createShape(elementData);
canvas.addShape(tempShape, rootElement);
var gfx = svgClone(elementRegistry.getGraphics(tempShape));
canvas.removeShape(tempShape);
return gfx;
};
moveShape = function(shape, target, position) {
var startPosition = {
x: shape.x + 10 + (shape.width / 2),
y: shape.y + 30 + (shape.height / 2)
};
move.start(canvasEvent(startPosition), shape);
dragging.hover({
element: target,
gfx: elementRegistry.getGraphics(target)
});
dragging.move(canvasEvent(position));
};
}));
it('should draw replaced visuals at correct position', inject(function(dragging) {
// when
moveShape(startEvent1, rootElement, { x: 280, y: 120 });
// then
var dragGroup = dragging.context().data.context.dragGroup;
svgAttr(dragGroup.childNodes[0], 'display', 'inline');
expect(dragGroup.childNodes[0].getBBox()).to.eql(dragGroup.childNodes[1].getBBox());
}));
skipCI('Mac OS') && it('should add dragger to context.visualReplacements once', inject(function(dragging) {
// when
moveShape(startEvent1, rootElement, { x: 275, y: 120 });
moveShape(startEvent1, rootElement, { x: 280, y: 120 });
moveShape(startEvent1, rootElement, { x: 285, y: 120 });
// then
var visualReplacements = dragging.context().data.context.visualReplacements;
expect(visualReplacements[startEvent1.id]).to.exist;
expect(Object.keys(visualReplacements).length).to.equal(1);
}));
skipCI('Mac OS') && it('should remove dragger from context.visualReplacements', inject(
function(elementRegistry, dragging) {
// given
var subProcess2 = elementRegistry.get('SubProcess_2');
// when
moveShape(startEvent1, rootElement, { x: 275, y: 120 });
moveShape(startEvent1, rootElement, { x: 280, y: 120 });
moveShape(startEvent1, subProcess2, { x: 350, y: 120 });
// then
var visualReplacements = dragging.context().data.context.visualReplacements;
expect(visualReplacements).to.be.empty;
}
));
it('should hide the replaced visual', inject(function(dragging) {
// when
moveShape(startEvent1, rootElement, { x: 280, y: 120 });
// then
var dragGroup = dragging.context().data.context.dragGroup;
expect(svgAttr(dragGroup.childNodes[0], 'display')).to.equal('none');
}));
it('should not replace non-interrupting start event while hover over same event sub process',
inject(function(dragging, elementRegistry) {
// given
var subProcess1 = elementRegistry.get('SubProcess.1');
// when
moveShape(startEvent1, subProcess1, { x: 210, y: 180 });
var context = dragging.context().data.context;
// then
// check if the visual representation remains a non interrupting message start event
var startEventGfx = getGfx({
type: 'bpmn:StartEvent',
isInterrupting: false,
eventDefinitionType: 'bpmn:MessageEventDefinition'
});
expect(innerSVG(context.dragGroup.childNodes[0])).to.equal(innerSVG(startEventGfx));
})
);
it('should replace non-interrupting start event while hover over root element',
inject(function(dragging, elementRegistry) {
// when
moveShape(startEvent1, rootElement, { x: 280, y: 120 });
var context = dragging.context().data.context;
// then
// check if the visual replacement is a blank interrupting start event
var startEventGfx = getGfx({ type: 'bpmn:StartEvent' });
expect(innerSVG(context.dragGroup.childNodes[1])).to.equal(innerSVG(startEventGfx));
})
);
it('should not replace non-interrupting start event while hover over another event sub process',
inject(function(dragging, elementRegistry) {
// given
var subProcess2 = elementRegistry.get('SubProcess_2');
// when
moveShape(startEvent1, subProcess2, { x: 350, y: 120 });
var context = dragging.context().data.context;
// then
// check if the visual representation remains a non interrupting message start event
var startEventGfx = getGfx({
type: 'bpmn:StartEvent',
isInterrupting: false,
eventDefinitionType: 'bpmn:MessageEventDefinition'
});
expect(innerSVG(context.dragGroup.childNodes[0])).to.equal(innerSVG(startEventGfx));
})
);
it('should replace non-interrupting start event while hover over regular sub process',
inject(function(dragging, elementRegistry) {
// given
var subProcess3 = elementRegistry.get('SubProcess_3');
// when
moveShape(startEvent1, subProcess3, { x: 600, y: 120 });
var context = dragging.context().data.context;
// then
// check if the visual representation remains a non interrupting message start event
var startEventGfx = getGfx({ type: 'bpmn:StartEvent' });
expect(innerSVG(context.dragGroup.childNodes[1])).to.equal(innerSVG(startEventGfx));
})
);
it('should replace all non-interrupting start events in a selection of multiple elements',
inject(function(move, dragging, elementRegistry, selection) {
// given
var startEvent2 = elementRegistry.get('StartEvent.2'),
startEvent3 = elementRegistry.get('StartEvent.3');
// when
selection.select([ startEvent1, startEvent2, startEvent3 ]);
moveShape(startEvent1, rootElement, { x: 150, y: 250 });
var context = dragging.context().data.context;
// then
// check if the visual replacements are blank interrupting start events
var startEventGfx = getGfx({ type: 'bpmn:StartEvent' });
expect(innerSVG(context.dragGroup.childNodes[1])).to.equal(innerSVG(startEventGfx));
expect(innerSVG(context.dragGroup.childNodes[3])).to.equal(innerSVG(startEventGfx));
expect(innerSVG(context.dragGroup.childNodes[4])).to.equal(innerSVG(startEventGfx));
})
);
it('should not replace any non-interrupting start events in a selection of multiple elements',
inject(function(move, dragging, elementRegistry, selection) {
// given
var startEvent2 = elementRegistry.get('StartEvent.2'),
startEvent3 = elementRegistry.get('StartEvent.3'),
subProcess2 = elementRegistry.get('SubProcess_2');
var messageStartEventGfx = getGfx({
type: 'bpmn:StartEvent',
isInterrupting: false,
eventDefinitionType: 'bpmn:MessageEventDefinition'
});
var timerStartEventGfx = getGfx({
type: 'bpmn:StartEvent',
isInterrupting: false,
eventDefinitionType: 'bpmn:TimerEventDefinition'
});
var startEventGfx = getGfx({ type: 'bpmn:StartEvent' });
// when
selection.select([ startEvent1, startEvent2, startEvent3 ]);
moveShape(startEvent1, subProcess2, { x: 350, y: 120 });
var context = dragging.context().data.context;
// then
expect(innerSVG(context.dragGroup.childNodes[0])).to.equal(innerSVG(messageStartEventGfx));
expect(innerSVG(context.dragGroup.childNodes[1])).to.equal(innerSVG(startEventGfx));
expect(innerSVG(context.dragGroup.childNodes[2])).to.equal(innerSVG(timerStartEventGfx));
})
);
it('should not throw TypeError when moving boundaryEvent',
inject(function(move, dragging, elementRegistry, elementFactory, selection, modeling) {
// given
var startEvent1 = elementRegistry.get('StartEvent.1'),
subProcess3 = elementRegistry.get('SubProcess_3');
var intermediateEvent = elementFactory.createShape({ type: 'bpmn:IntermediateThrowEvent' });
var boundaryEvent = modeling.createShape(
intermediateEvent,
{ x: 550, y: 180 },
subProcess3,
true
);
// when
selection.select([ startEvent1 ]);
moveShape(boundaryEvent, subProcess3, { x: 580, y: 210 });
moveShape(boundaryEvent, subProcess3, { x: 580, y: 180 });
// then
// expect not to throw TypeError: Cannot read property 'oldElementId' of undefined
})
);
});
// helpers /////////////
function skipCI(userAgent) {
const ci = window.__env__ && window.__env__.CI;
if (!ci) {
return false;
}
if (userAgent) {
return window.navigator.userAgent.includes(userAgent);
}
return true;
}
================================================
FILE: test/spec/features/rules/BpmnRules.attaching.bpmn
================================================
SequenceFlow_1pdic6v
SequenceFlow_106kcuw
SequenceFlow_106kcuw
SequenceFlow_1pdic6v
================================================
FILE: test/spec/features/rules/BpmnRules.boundaryEvent.bpmn
================================================
Boundary Events are awesome!
================================================
FILE: test/spec/features/rules/BpmnRules.collaboration-dataAssociation.bpmn
================================================
DataStoreReference
Property_1oyztzi
================================================
FILE: test/spec/features/rules/BpmnRules.collaboration-empty.bpmn
================================================
================================================
FILE: test/spec/features/rules/BpmnRules.collaboration-lanes.bpmn
================================================
Boundary
Task_Boundary
Task
SequenceFlow_from_Boundary
SequenceFlow
SequenceFlow_from_Boundary
SequenceFlow
================================================
FILE: test/spec/features/rules/BpmnRules.collaboration.bpmn
================================================
SequenceFlow
SequenceFlow
DataStoreReference
================================================
FILE: test/spec/features/rules/BpmnRules.collapsedPools.bpmn
================================================
================================================
FILE: test/spec/features/rules/BpmnRules.compensation-collaboration.bpmn
================================================
================================================
FILE: test/spec/features/rules/BpmnRules.compensation.bpmn
================================================
================================================
FILE: test/spec/features/rules/BpmnRules.connectOnCreate.bpmn
================================================
================================================
FILE: test/spec/features/rules/BpmnRules.dataAssociation.bpmn
================================================
DataObjectReference
================================================
FILE: test/spec/features/rules/BpmnRules.dataInputOutput.collaboration.bpmn
================================================
DataInput
DataOutput
_9628422b-85a6-4857-8c14-7289b9fd9a8a
_29b8c649-e2a0-4dd3-804b-567e8cc71718
DataInput
Property_1wt0ffm
_29b8c649-e2a0-4dd3-804b-567e8cc71718
DataOutput
================================================
FILE: test/spec/features/rules/BpmnRules.dataInputOutput.process.bpmn
================================================
DataInput
DataOutput
Task_DataInput
Task_DataOutput
DataInput
Task_DataInput
Task_DataOutput
DataOutput
================================================
FILE: test/spec/features/rules/BpmnRules.detaching.bpmn
================================================
SequenceFlow_06au3yc
SequenceFlow_06au3yc
================================================
FILE: test/spec/features/rules/BpmnRules.eventBasedGatewayBasic.bpmn
================================================
================================================
FILE: test/spec/features/rules/BpmnRules.groups.bpmn
================================================
================================================
FILE: test/spec/features/rules/BpmnRules.insert.bpmn
================================================
S1
S2
S1
S2
================================================
FILE: test/spec/features/rules/BpmnRules.messageFlow.bpmn
================================================
================================================
FILE: test/spec/features/rules/BpmnRules.moveLane.bpmn
================================================
================================================
FILE: test/spec/features/rules/BpmnRules.multiSelectionPools.bpmn
================================================
================================================
FILE: test/spec/features/rules/BpmnRules.process.bpmn
================================================
SequenceFlow
SequenceFlow
================================================
FILE: test/spec/features/rules/BpmnRules.subProcess-dataAssociation.bpmn
================================================
DataStoreReference
Property_0x4gewx
DataStoreReference
================================================
FILE: test/spec/features/rules/BpmnRulesSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import {
expectCanConnect,
expectCanCopy,
expectCanCreate,
expectCanDrop,
expectCanInsert,
expectCanMove
} from './Helper';
import modelingModule from 'lib/features/modeling';
import coreModule from 'lib/core';
describe('features/modeling/rules - BpmnRules', function() {
var testModules = [ coreModule, modelingModule ];
describe('create elements', function() {
var testXML = require('./BpmnRules.process.bpmn');
beforeEach(bootstrapModeler(testXML, { modules: testModules }));
it('create tasks -> process', inject(function(elementFactory) {
// given
var task1 = elementFactory.createShape({ type: 'bpmn:Task' }),
task2 = elementFactory.createShape({ type: 'bpmn:Task' });
// then
expectCanCreate([ task1, task2 ], 'Process', true);
}));
it('create tasks -> task', inject(function(elementFactory) {
// given
var task1 = elementFactory.createShape({ type: 'bpmn:Task' }),
task2 = elementFactory.createShape({ type: 'bpmn:Task' });
// then
expectCanCreate([ task1, task2 ], 'Task', false);
}));
it('create tasks and sequence flow -> process', inject(function(elementFactory) {
// given
var task1 = elementFactory.createShape({ type: 'bpmn:Task' }),
task2 = elementFactory.createShape({ type: 'bpmn:Task' }),
sequenceFlow = elementFactory.createConnection({
type: 'bpmn:SequenceFlow',
source: task1,
target: task2
});
// then
expectCanCreate([ task1, task2, sequenceFlow ], 'Process', true);
}));
it('create tasks and message flow -> process', inject(function(elementFactory) {
// given
var task1 = elementFactory.createShape({ type: 'bpmn:Task' }),
task2 = elementFactory.createShape({ type: 'bpmn:Task' }),
messageFlow = elementFactory.createConnection({
type: 'bpmn:MessageFlow',
source: task1,
target: task2
});
// then
expectCanCreate([ task1, task2, messageFlow ], 'Process', false);
}));
it('create task and non-interrupting boundary event', inject(function(elementFactory) {
// given
var task = elementFactory.createShape({ type: 'bpmn:Task' }),
boundaryEvent = elementFactory.createShape({
type: 'bpmn:BoundaryEvent',
eventDefinitionType: 'bpmn:EscalationEventDefinition',
cancelActivity: false,
host: task
});
// then
expectCanCreate([ task, boundaryEvent ], 'Process', true);
}));
it('create task and interrupting boundary event', inject(function(elementFactory) {
// given
var task = elementFactory.createShape({ type: 'bpmn:Task' }),
boundaryEvent = elementFactory.createShape({
type: 'bpmn:BoundaryEvent',
eventDefinitionType: 'bpmn:EscalationEventDefinition',
host: task
});
// then
expectCanCreate([ task, boundaryEvent ], 'Process', true);
}));
it('can\'t create multiple elements on flow', inject(function(elementFactory) {
// given
var task1 = elementFactory.createShape({ type: 'bpmn:Task' }),
task2 = elementFactory.createShape({ type: 'bpmn:Task' });
// then
expectCanCreate([ task1, task2 ], 'SequenceFlow', false);
}));
describe('empty pool', function() {
var testXML = require('./BpmnRules.collaboration-empty.bpmn');
beforeEach(bootstrapModeler(testXML, { modules: testModules }));
it('should not allow to drop DataStoreReference when there is no process to append to',
inject(function(elementFactory) {
// given
var dataStoreReference = elementFactory.createShape({ type: 'bpmn:DataStoreReference' });
// then
expectCanCreate(dataStoreReference, 'Collaboration', false);
})
);
});
});
describe('copy elements', function() {
var testXML = require('./BpmnRules.process.bpmn');
beforeEach(bootstrapModeler(testXML, { modules: testModules }));
it('copy task', inject(function(elementFactory) {
// given
var task1 = elementFactory.createShape({ type: 'bpmn:Task' });
// then
expectCanCopy(task1, [ task1 ], true);
}));
it('copy label', inject(function(elementFactory) {
// given
var task = elementFactory.createShape({ type: 'bpmn:Task' }),
label = elementFactory.createLabel({ labelTarget: task });
// then
// copying labels should always be allowed
expectCanCopy(label, [], true);
}));
it('copy lane with parent participant', inject(function(elementFactory) {
// given
var participant = elementFactory.createShape({ type: 'bpmn:Participant' }),
lane = elementFactory.createShape({ type: 'bpmn:Lane', parent: participant });
// then
expectCanCopy(lane, [ participant ], true);
}));
it('copy lane without parent participant', inject(function(elementFactory) {
// given
var participant = elementFactory.createShape({ type: 'bpmn:Participant' }),
lane = elementFactory.createShape({ type: 'bpmn:Lane', parent: participant });
// then
expectCanCopy(lane, [], false);
}));
it('copy boundary event with host', inject(function(elementFactory) {
// given
var task = elementFactory.createShape({ type: 'bpmn:Task' }),
boundaryEvent = elementFactory.createShape({ type: 'bpmn:BoundaryEvent', host: task });
// then
expectCanCopy(boundaryEvent, [ task ], true);
}));
it('copy boundary event without host', inject(function(elementFactory) {
// given
var task = elementFactory.createShape({ type: 'bpmn:Task' }),
boundaryEvent = elementFactory.createShape({ type: 'bpmn:BoundaryEvent', host: task });
// then
expectCanCopy(boundaryEvent, [ boundaryEvent ], true);
}));
});
describe('on process diagram', function() {
var testXML = require('./BpmnRules.process.bpmn');
beforeEach(bootstrapModeler(testXML, { modules: testModules }));
it('connect StartEvent_None -> Task', inject(function() {
expectCanConnect('StartEvent_None', 'Task', {
sequenceFlow: true,
messageFlow: false,
association: false,
dataAssociation: false
});
}));
it('connect StartEvent_None -> TextAnnotation', inject(function() {
expectCanConnect('StartEvent_None', 'TextAnnotation', {
sequenceFlow: false,
messageFlow: false,
association: true,
dataAssociation: false
});
}));
it('connect SequenceFlow -> TextAnnotation', inject(function() {
expectCanConnect('SequenceFlow', 'TextAnnotation', {
sequenceFlow: false,
messageFlow: false,
association: true,
dataAssociation: false
});
}));
it('connect Task -> IntermediateThrowEvent_Link', inject(function() {
expectCanConnect('Task', 'IntermediateThrowEvent_Link', {
sequenceFlow: true,
messageFlow: false,
association: false,
dataAssociation: false
});
}));
it('connect IntermediateThrowEvent_Link -> EndEvent_None', inject(function() {
expectCanConnect('IntermediateThrowEvent_Link', 'EndEvent_None', {
sequenceFlow: false,
messageFlow: false,
association: false,
dataAssociation: false
});
}));
it('connect StartEvent_None -> IntermediateCatchEvent_Link', inject(function() {
expectCanConnect('StartEvent_None', 'IntermediateCatchEvent_Link', {
sequenceFlow: false,
messageFlow: false,
association: false,
dataAssociation: false
});
}));
it('connect IntermediateCatchEvent_Link -> Task', inject(function() {
expectCanConnect('IntermediateCatchEvent_Link', 'Task', {
sequenceFlow: true,
messageFlow: false,
association: false,
dataAssociation: false
});
}));
it('drop TextAnnotation -> Process', inject(function() {
expectCanDrop('TextAnnotation', 'Process', true);
}));
it('drop TextAnnotation -> SubProcess', inject(function() {
expectCanDrop('TextAnnotation', 'SubProcess', true);
}));
it('drop Start Event -> Collapsed Sub Process', function() {
expectCanDrop('StartEvent_None', 'CollapsedSubProcess', false);
});
it('connect DataObjectReference -> StartEvent_None', inject(function() {
expectCanConnect('DataObjectReference', 'StartEvent_None', {
sequenceFlow: false,
messageFlow: false,
association: false,
dataAssociation: false
});
}));
it('connect StartEvent_None -> DataObjectReference', inject(function() {
expectCanConnect('StartEvent_None', 'DataObjectReference', {
sequenceFlow: false,
messageFlow: false,
association: true,
dataAssociation: { type: 'bpmn:DataOutputAssociation' }
});
}));
it('connect DataObjectReference -> EndEvent_None', inject(function() {
expectCanConnect('DataObjectReference', 'EndEvent_None', {
sequenceFlow: false,
messageFlow: false,
association: true,
dataAssociation: { type: 'bpmn:DataInputAssociation' }
});
}));
it('connect EndEvent_None -> DataObjectReference', inject(function() {
expectCanConnect('EndEvent_None', 'DataObjectReference', {
sequenceFlow: false,
messageFlow: false,
association: false,
dataAssociation: false
});
}));
it('connect Task -> DataObjectReference', inject(function() {
expectCanConnect('Task', 'DataObjectReference', {
sequenceFlow: false,
messageFlow: false,
association: true,
dataAssociation: { type: 'bpmn:DataOutputAssociation' }
});
}));
it('connect DataObjectReference -> Task', inject(function() {
expectCanConnect('DataObjectReference', 'Task', {
sequenceFlow: false,
messageFlow: false,
association: true,
dataAssociation: { type: 'bpmn:DataInputAssociation' }
});
}));
it('connect SubProcess -> DataObjectReference', inject(function() {
expectCanConnect('SubProcess', 'DataObjectReference', {
sequenceFlow: false,
messageFlow: false,
association: true,
dataAssociation: { type: 'bpmn:DataOutputAssociation' }
});
}));
it('connect DataObjectReference -> SubProcess', inject(function() {
expectCanConnect('DataObjectReference', 'SubProcess', {
sequenceFlow: false,
messageFlow: false,
association: true,
dataAssociation: { type: 'bpmn:DataInputAssociation' }
});
}));
it('connect DataStoreReference -> StartEvent_None', inject(function() {
expectCanConnect('DataStoreReference', 'StartEvent_None', {
sequenceFlow: false,
messageFlow: false,
association: false,
dataAssociation: false
});
}));
it('connect DataObjectReference -> TextAnnotation', inject(function() {
expectCanConnect('DataObjectReference', 'TextAnnotation', {
sequenceFlow: false,
messageFlow: false,
association: true,
dataAssociation: false
});
}));
it('connect TextAnnotation -> DataObjectReference', inject(function() {
expectCanConnect('TextAnnotation', 'DataObjectReference', {
sequenceFlow: false,
messageFlow: false,
association: true,
dataAssociation: false
});
}));
it('connect DataStoreReference -> TextAnnotation', inject(function() {
expectCanConnect('DataStoreReference', 'TextAnnotation', {
sequenceFlow: false,
messageFlow: false,
association: true,
dataAssociation: false
});
}));
it('connect TextAnnotation -> DataStoreReference', inject(function() {
expectCanConnect('TextAnnotation', 'DataStoreReference', {
sequenceFlow: false,
messageFlow: false,
association: true,
dataAssociation: false
});
}));
it('connect Group -> TextAnnotation', inject(function() {
expectCanConnect('Group', 'TextAnnotation', {
sequenceFlow: false,
messageFlow: false,
association: true,
dataAssociation: false
});
}));
it('connect TextAnnotation -> Group', inject(function() {
expectCanConnect('TextAnnotation', 'Group', {
sequenceFlow: false,
messageFlow: false,
association: true,
dataAssociation: false
});
}));
it('connect StartEvent_None -> DataStoreReference', inject(function() {
expectCanConnect('StartEvent_None', 'DataStoreReference', {
sequenceFlow: false,
messageFlow: false,
association: true,
dataAssociation: { type: 'bpmn:DataOutputAssociation' }
});
}));
it('connect DataStoreReference -> EndEvent_None', inject(function() {
expectCanConnect('DataStoreReference', 'EndEvent_None', {
sequenceFlow: false,
messageFlow: false,
association: true,
dataAssociation: { type: 'bpmn:DataInputAssociation' }
});
}));
it('connect EndEvent_None -> DataStoreReference', inject(function() {
expectCanConnect('EndEvent_None', 'DataStoreReference', {
sequenceFlow: false,
messageFlow: false,
association: false,
dataAssociation: false
});
}));
it('connect Task -> DataStoreReference', inject(function() {
expectCanConnect('Task', 'DataStoreReference', {
sequenceFlow: false,
messageFlow: false,
association: true,
dataAssociation: { type: 'bpmn:DataOutputAssociation' }
});
}));
it('connect DataStoreReference -> Task', inject(function() {
expectCanConnect('DataStoreReference', 'Task', {
sequenceFlow: false,
messageFlow: false,
association: true,
dataAssociation: { type: 'bpmn:DataInputAssociation' }
});
}));
it('connect SubProcess -> DataStoreReference', inject(function() {
expectCanConnect('SubProcess', 'DataStoreReference', {
sequenceFlow: false,
messageFlow: false,
association: true,
dataAssociation: { type: 'bpmn:DataOutputAssociation' }
});
}));
it('connect DataStoreReference -> SubProcess', inject(function() {
expectCanConnect('DataStoreReference', 'SubProcess', {
sequenceFlow: false,
messageFlow: false,
association: true,
dataAssociation: { type: 'bpmn:DataInputAssociation' }
});
}));
it('connect Task -> Task', inject(function() {
expectCanConnect('Task', 'Task', {
sequenceFlow: true,
messageFlow: false,
association: false,
dataAssociation: false
});
}));
});
describe('boundary events', function() {
var testXML = require('./BpmnRules.boundaryEvent.bpmn');
beforeEach(bootstrapModeler(testXML, { modules: testModules }));
it('connect BoundaryEvent_on_SubProcess -> Task', inject(function() {
expectCanConnect('BoundaryEvent_on_SubProcess', 'Task', {
sequenceFlow: true,
messageFlow: false,
association: false,
dataAssociation: false
});
}));
it('connect BoundaryEvent_on_SubProcess -> ExclusiveGateway', inject(function() {
expectCanConnect('BoundaryEvent_on_SubProcess', 'ExclusiveGateway', {
sequenceFlow: true,
messageFlow: false,
association: false,
dataAssociation: false
});
}));
it('connect BoundaryEvent_on_SubProcess -> SubProcess', inject(function() {
expectCanConnect('BoundaryEvent_on_SubProcess', 'SubProcess', {
sequenceFlow: true,
messageFlow: false,
association: false,
dataAssociation: false
});
}));
it('connect BoundaryEvent_on_SubProcess -> BoundaryEvent_on_Task', inject(function() {
expectCanConnect('BoundaryEvent_on_SubProcess', 'BoundaryEvent_on_Task', {
sequenceFlow: false,
messageFlow: false,
association: false,
dataAssociation: false
});
}));
it('connect BoundaryEvent_on_SubProcess -> StartEvent_None', inject(function() {
expectCanConnect('BoundaryEvent_on_SubProcess', 'StartEvent_None', {
sequenceFlow: false,
messageFlow: false,
association: false,
dataAssociation: false
});
}));
it('connect StartEvent_None -> BoundaryEvent_on_SubProcess', inject(function() {
expectCanConnect('StartEvent_None', 'BoundaryEvent_on_SubProcess', {
sequenceFlow: false,
messageFlow: false,
association: false,
dataAssociation: false
});
}));
it('connect BoundaryEvent_nested -> Task', inject(function() {
expectCanConnect('BoundaryEvent_nested', 'Task', {
sequenceFlow: false,
messageFlow: false,
association: false,
dataAssociation: false
});
}));
it('connect BoundaryEvent_nested -> EndEvent_nested', inject(function() {
expectCanConnect('BoundaryEvent_nested', 'EndEvent_nested', {
sequenceFlow: true,
messageFlow: false,
association: false,
dataAssociation: false
});
}));
it('connect BoundaryEvent_on_SubProcess -> BoundaryEvent_in_OtherProcess', inject(function() {
expectCanConnect('BoundaryEvent_on_SubProcess', 'BoundaryEvent_in_OtherProcess', {
sequenceFlow: false,
messageFlow: false,
association: false,
dataAssociation: false
});
}));
it('connect BoundaryEvent_on_SubProcess -> Task_in_OtherProcess', inject(function() {
expectCanConnect('BoundaryEvent_on_SubProcess', 'Task_in_OtherProcess', {
sequenceFlow: false,
messageFlow: false,
association: false,
dataAssociation: false
});
}));
it('connect Task_in_OtherProcess -> BoundaryEvent_on_SubProcess', inject(function() {
expectCanConnect('Task_in_OtherProcess', 'BoundaryEvent_on_SubProcess', {
sequenceFlow: false,
messageFlow: false,
association: false,
dataAssociation: false
});
}));
it('connect Task_in_OtherProcess -> MessageBoundaryEvent_onSubProcess', inject(function() {
expectCanConnect('Task_in_OtherProcess', 'MessageBoundaryEvent_onSubProcess', {
sequenceFlow: false,
messageFlow: true,
association: false,
dataAssociation: false
});
}));
it('drop BoundaryEvent -> Task', function() {
expectCanDrop('BoundaryEvent_on_SubProcess', 'Task_in_OtherProcess', false);
});
});
describe('compensation', function() {
var testXML = require('./BpmnRules.compensation.bpmn');
beforeEach(bootstrapModeler(testXML, { modules: testModules }));
it('connect CompensationBoundary -> NoneTask', inject(function() {
expectCanConnect('CompensationBoundary', 'NoneTask', {
sequenceFlow: false,
messageFlow: false,
association: false,
dataAssociation: false,
compensationAssociation: true
});
}));
it('connect CompensationBoundary -> SubProcess', inject(function() {
expectCanConnect('CompensationBoundary', 'SubProcess_2', {
sequenceFlow: false,
messageFlow: false,
association: false,
dataAssociation: false,
compensationAssociation: true
});
}));
it('connect CompensationBoundary -> Event SubProcess', inject(function() {
expectCanConnect('CompensationBoundary', 'SubProcess_1', {
sequenceFlow: false,
messageFlow: false,
association: false,
dataAssociation: false,
compensationAssociation: false
});
}));
it('connect CompensationBoundary -> TaskForCompensation', inject(function() {
expectCanConnect('CompensationBoundary', 'TaskForCompensation', {
sequenceFlow: false,
messageFlow: false,
association: false,
dataAssociation: false,
compensationAssociation: true
});
}));
it('connect CompensationBoundary -> Gateway', inject(function() {
expectCanConnect('CompensationBoundary', 'Gateway', {
sequenceFlow: false,
messageFlow: false,
association: false,
dataAssociation: false,
compensationAssociation: false
});
}));
it('connect CompensationBoundary -> IntermediateEvent', inject(function() {
expectCanConnect('CompensationBoundary', 'IntermediateEvent', {
sequenceFlow: false,
messageFlow: false,
association: false,
dataAssociation: false,
compensationAssociation: false
});
}));
it('connect Task -> TaskForCompensation', inject(function() {
expectCanConnect('Task', 'TaskForCompensation', {
sequenceFlow: false,
messageFlow: false,
association: false,
dataAssociation: false,
compensationAssociation: false
});
}));
it('connect TaskForCompensation -> Task', inject(function() {
expectCanConnect('TaskForCompensation', 'Task', {
sequenceFlow: false,
messageFlow: false,
association: false,
dataAssociation: false,
compensationAssociation: false
});
}));
it('connect CompensationBoundary -> TaskInSubprocess', function() {
expectCanConnect('CompensationBoundary', 'TaskInSubprocess', {
sequenceFlow: false,
messageFlow: false,
association: false,
dataAssociation: false,
compensationAssociation: false
});
});
it('connect CompensationBoundary -> Host (Task)', function() {
expectCanConnect('CompensationBoundary', 'Task', {
sequenceFlow: false,
messageFlow: false,
association: false,
dataAssociation: false,
compensationAssociation: false
});
});
it('connect CompensationBoundary -> TaskWithBoundary', function() {
expectCanConnect('CompensationBoundary', 'TaskWithBoundary', {
sequenceFlow: false,
messageFlow: false,
association: false,
dataAssociation: false,
compensationAssociation: true
});
});
});
describe('compensation in collaboration', function() {
var testXML = require('./BpmnRules.compensation-collaboration.bpmn');
beforeEach(bootstrapModeler(testXML, { modules: testModules }));
it('connect CompensationTask -> CollapsedPool', inject(function() {
expectCanConnect('CompensationTask', 'CollapsedPool', {
sequenceFlow: false,
messageFlow: true,
association: false,
dataAssociation: false
});
}));
it('connect CollapsedPool -> CompensationTask', inject(function() {
expectCanConnect('CollapsedPool', 'CompensationTask', {
sequenceFlow: false,
messageFlow: false,
association: false,
dataAssociation: false
});
}));
});
describe('on collaboration diagram', function() {
var testXML = require('./BpmnRules.collaboration.bpmn');
beforeEach(bootstrapModeler(testXML, { modules: testModules }));
it('connect StartEvent_None -> IntermediateEvent', inject(function() {
expectCanConnect('StartEvent_None', 'IntermediateThrowEvent_Message', {
sequenceFlow: true,
messageFlow: false,
association: false,
dataAssociation: false
});
}));
it('connect StartEvent_None -> OtherParticipant', inject(function() {
expectCanConnect('StartEvent_None', 'OtherParticipant', {
sequenceFlow: false,
messageFlow: false,
association: false,
dataAssociation: false
});
}));
it('connect OtherParticipant -> StartEvent_None', inject(function() {
expectCanConnect('OtherParticipant', 'StartEvent_None', {
sequenceFlow: false,
messageFlow: true,
association: false,
dataAssociation: false
});
}));
it('connect OtherParticipant -> StartEvent_Timer', inject(function() {
expectCanConnect('OtherParticipant', 'StartEvent_Timer', {
sequenceFlow: false,
messageFlow: false,
association: false,
dataAssociation: false
});
}));
it('connect OtherParticipant -> StartEvent_Message', inject(function() {
expectCanConnect('OtherParticipant', 'StartEvent_Message', {
sequenceFlow: false,
messageFlow: true,
association: false,
dataAssociation: false
});
}));
it('connect EndEvent_None -> OtherParticipant', inject(function() {
expectCanConnect('EndEvent_None', 'OtherParticipant', {
sequenceFlow: false,
messageFlow: true,
association: false,
dataAssociation: false
});
}));
it('connect EndEvent_Cancel -> OtherParticipant', inject(function() {
expectCanConnect('EndEvent_Cancel', 'OtherParticipant', {
sequenceFlow: false,
messageFlow: false,
association: false,
dataAssociation: false
});
}));
it('connect EndEvent_Message -> OtherParticipant', inject(function() {
expectCanConnect('EndEvent_Message', 'OtherParticipant', {
sequenceFlow: false,
messageFlow: true,
association: false,
dataAssociation: false
});
}));
it('connect OtherParticipant -> EndEvent_None', inject(function() {
expectCanConnect('OtherParticipant', 'EndEvent_None', {
sequenceFlow: false,
messageFlow: false,
association: false,
dataAssociation: false
});
}));
it('connect IntermediateThrowEvent_Message -> OtherParticipant', inject(function() {
expectCanConnect('IntermediateThrowEvent_Message', 'OtherParticipant', {
sequenceFlow: false,
messageFlow: true,
association: false,
dataAssociation: false
});
}));
it('connect IntermediateThrowEvent_None -> OtherParticipant', inject(function() {
expectCanConnect('IntermediateThrowEvent_None', 'OtherParticipant', {
sequenceFlow: false,
messageFlow: true,
association: false,
dataAssociation: false
});
}));
it('connect IntermediateThrowEvent_Signal -> OtherParticipant', inject(function() {
expectCanConnect('IntermediateThrowEvent_Signal', 'OtherParticipant', {
sequenceFlow: false,
messageFlow: false,
association: false,
dataAssociation: false
});
}));
it('connect OtherParticipant -> IntermediateThrowEvent_Message', inject(function() {
expectCanConnect('OtherParticipant', 'IntermediateThrowEvent_Message', {
sequenceFlow: false,
messageFlow: false,
association: false,
dataAssociation: false
});
}));
it('connect Task_in_SubProcess -> OtherParticipant', inject(function() {
expectCanConnect('Task_in_SubProcess', 'OtherParticipant', {
sequenceFlow: false,
messageFlow: true,
association: false,
dataAssociation: false
});
}));
it('connect EndEvent_None_in_SubProcess -> OtherParticipant', inject(function() {
expectCanConnect('EndEvent_None_in_SubProcess', 'OtherParticipant', {
sequenceFlow: false,
messageFlow: true,
association: false,
dataAssociation: false
});
}));
it('connect OtherParticipant -> Task_in_SubProcess', inject(function() {
expectCanConnect('OtherParticipant', 'Task_in_SubProcess', {
sequenceFlow: false,
messageFlow: true,
association: false,
dataAssociation: false
});
}));
it('connect Participant -> OtherParticipant', inject(function() {
expectCanConnect('Participant', 'OtherParticipant', {
sequenceFlow: false,
messageFlow: true,
association: false,
dataAssociation: false
});
}));
it('connect StartEvent_None -> TextAnnotation_OtherParticipant', inject(function() {
expectCanConnect('StartEvent_None', 'TextAnnotation_OtherParticipant', {
sequenceFlow: false,
messageFlow: false,
association: true,
dataAssociation: false
});
}));
it('connect CallActivity -> Participant', function() {
expectCanConnect('CallActivity', 'Participant', {
sequenceFlow: false,
messageFlow: true,
association: false,
dataAssociation: false
});
});
it('connect MessageFlow -> TextAnnotation', inject(function() {
expectCanConnect('MessageFlow_2', 'TextAnnotation_Global', {
sequenceFlow: false,
messageFlow: false,
association: true,
dataAssociation: false
});
}));
it('drop TextAnnotation_Global -> Participant', inject(function() {
expectCanDrop('TextAnnotation_Global', 'Participant', true);
}));
it('drop DataStoreReference -> Collaboration', function() {
expectCanDrop('DataStoreReference', 'Collaboration', true);
});
it('drop element -> collapsed Participant', inject(function() {
expectCanDrop('StartEvent_None', 'CollapsedParticipant', false);
expectCanDrop('SubProcess', 'CollapsedParticipant', false);
expectCanDrop('Task_in_SubProcess', 'CollapsedParticipant', false);
expectCanDrop('TextAnnotation_Global', 'CollapsedParticipant', false);
}));
describe('drop MessageFlow label', function() {
var label;
beforeEach(inject(function(elementRegistry) {
label = elementRegistry.get('MessageFlow_labeled').label;
}));
it('-> MessageFlow', function() {
expectCanDrop(label, 'MessageFlow_labeled', true);
});
it('-> CollapsedParticipant', function() {
expectCanDrop(label, 'CollapsedParticipant', true);
});
it('-> Collaboration', function() {
// then
expectCanDrop(label, 'Collaboration', true);
});
it('-> Task_in_SubProcess', function() {
expectCanDrop(label, 'Task_in_SubProcess', true);
});
it('-> SequenceFlow', function() {
expectCanDrop(label, 'SequenceFlow', true);
});
it('-> DataOutputAssociation', function() {
expectCanDrop(label, 'DataOutputAssociation', true);
});
it('-> Group', function() {
expectCanDrop(label, 'Group', true);
});
});
describe('create Group', function() {
var group;
beforeEach(inject(function(elementFactory) {
group = elementFactory.createShape({
type: 'bpmn:Group',
x: 413, y: 254
});
}));
it('-> MessageFlow', function() {
expectCanCreate(group, 'MessageFlow_labeled', true);
});
it('-> CollapsedParticipant', function() {
expectCanCreate(group, 'CollapsedParticipant', true);
});
it('-> Collaboration', function() {
// then
expectCanCreate(group, 'Collaboration', true);
});
it('-> Task_in_SubProcess', function() {
expectCanCreate(group, 'Task_in_SubProcess', true);
});
it('-> SequenceFlow', function() {
expectCanCreate(group, 'SequenceFlow', true);
});
it('-> DataOutputAssociation', function() {
expectCanCreate(group, 'DataOutputAssociation', true);
});
it('-> Group', function() {
expectCanCreate(group, 'Group', true);
});
});
describe('reject create on Group', function() {
it('DataStoreReference ->', inject(function(elementFactory) {
var dataStoreReference = elementFactory.createShape({
type: 'bpmn:DataStoreReference',
x: 413, y: 254
});
expectCanCreate(dataStoreReference, 'Group', false);
}));
it('Task ->', inject(function(elementFactory) {
var task = elementFactory.createShape({
type: 'bpmn:Task',
x: 413, y: 254
});
expectCanCreate(task, 'Group', false);
}));
});
describe('reject create on label', function() {
var label;
beforeEach(inject(function(elementRegistry) {
label = elementRegistry.get('MessageFlow_labeled').label;
}));
it('DataStoreReference ->', inject(function(elementFactory) {
var dataStoreReference = elementFactory.createShape({
type: 'bpmn:DataStoreReference',
x: 413, y: 254
});
expectCanCreate(dataStoreReference, label, false);
}));
it('Task ->', inject(function(elementFactory) {
var task = elementFactory.createShape({
type: 'bpmn:Task',
x: 413, y: 254
});
expectCanCreate(task, label, false);
}));
});
});
describe('participants', function() {
var testXML = require('./BpmnRules.collapsedPools.bpmn');
beforeEach(bootstrapModeler(testXML, { modules: testModules }));
it('connect CollapsedPool_A -> CollapsedPool_B', inject(function() {
expectCanConnect('CollapsedPool_A', 'CollapsedPool_B', {
sequenceFlow: false,
messageFlow: true,
association: false,
dataAssociation: false
});
}));
it('connect CollapsedPool_A -> ExpandedPool', inject(function() {
expectCanConnect('CollapsedPool_A', 'ExpandedPool', {
sequenceFlow: false,
messageFlow: true,
association: false,
dataAssociation: false
});
}));
it('connect ExpandedPool -> CollapsedPool_A', inject(function() {
expectCanConnect('ExpandedPool', 'CollapsedPool_A', {
sequenceFlow: false,
messageFlow: true,
association: false,
dataAssociation: false
});
}));
});
describe('message flows', function() {
var testXML = require('./BpmnRules.messageFlow.bpmn');
beforeEach(bootstrapModeler(testXML, { modules: testModules }));
it('drop MessageFlow -> Collaboration', inject(function() {
expectCanDrop('MessageFlow', 'Collaboration', true);
}));
});
describe('data association move', function() {
describe('on process diagram', function() {
var testXML = require('./BpmnRules.dataAssociation.bpmn');
beforeEach(bootstrapModeler(testXML, { modules: testModules }));
it('move selection including data association', inject(function(elementRegistry) {
// when
var elements = [
elementRegistry.get('Task'),
elementRegistry.get('DataAssociation'),
elementRegistry.get('DataObjectReference')
];
// then
expectCanMove(elements, 'Process', {
attach: false,
move: true
});
}));
});
describe('on sub process', function() {
var testXML = require('./BpmnRules.subProcess-dataAssociation.bpmn');
beforeEach(bootstrapModeler(testXML, { modules: testModules }));
it('move task and data association', inject(function(elementRegistry) {
// when
var elements = [
elementRegistry.get('DataInputAssociation'),
elementRegistry.get('DataOutputAssociation'),
elementRegistry.get('DataStoreReference'),
elementRegistry.get('Task')
];
// then
expectCanMove(elements, 'SubProcess', {
attach: false,
move: true
});
}));
});
describe('on collaboration', function() {
var testXML = require('./BpmnRules.collaboration-dataAssociation.bpmn');
beforeEach(bootstrapModeler(testXML, { modules: testModules }));
it('move participant and data association', inject(function(elementRegistry) {
// when
var elements = [
elementRegistry.get('DataInputAssociation'),
elementRegistry.get('Participant')
];
// then
expectCanMove(elements, 'Collaboration', {
attach: false,
move: true
});
}));
});
});
describe('multi selection move', function() {
var testXML = require('./BpmnRules.multiSelectionPools.bpmn');
beforeEach(bootstrapModeler(testXML, { modules: testModules }));
it('is allowed across pools when parent does not change', inject(function(elementRegistry) {
// when
var elements = [
elementRegistry.get('Task_A'),
elementRegistry.get('Task_B'),
elementRegistry.get('MessageFlow_1v3u2fb')
];
var target = 'Participant_1';
// then
expectCanMove(elements, target, {
attach: false,
move: true
});
}));
});
describe('event move', function() {
var testXML = require('../../../fixtures/bpmn/boundary-events.bpmn');
beforeEach(bootstrapModeler(testXML, { modules: testModules }));
it('attach/move multiple BoundaryEvents -> SubProcess_1', inject(
function(elementRegistry) {
// when
var boundaryEvent = elementRegistry.get('BoundaryEvent_1'),
boundaryEvent2 = elementRegistry.get('BoundaryEvent_2');
// we assume boundary events and labels
// to be already filtered during move
var elements = [ boundaryEvent, boundaryEvent2 ];
// then
expectCanMove(elements, 'SubProcess_1', {
attach: false,
move: true
});
}
));
it('attach/move SubProcess, BoundaryEvent and label -> Process', inject(
function(elementRegistry) {
// when
var subProcess = elementRegistry.get('SubProcess_1'),
boundaryEvent = elementRegistry.get('BoundaryEvent_1'),
label = boundaryEvent.label;
// we assume boundary events and labels
// to be already filtered during move
var elements = [ subProcess, boundaryEvent, label ];
// then
expectCanMove(elements, 'Process_1', {
attach: false,
move: true
});
}
));
});
describe('event keyboard move', function() {
var testXML = require('./BpmnRules.boundaryEvent.bpmn');
beforeEach(bootstrapModeler(testXML, { modules: testModules }));
it('should NOT allow keyboard move of boundary event without host',
inject(function(elementRegistry, rules) {
// given
var boundaryEvent = elementRegistry.get('BoundaryEvent_on_Task');
// when
var canMove = rules.allowed('elements.move', {
shapes: [ boundaryEvent ],
hints: {
keyboardMove: true
}
});
// then
expect(canMove).to.be.false;
})
);
it('should allow keyboard move of boundary event with host',
inject(function(elementRegistry, rules) {
// given
var task = elementRegistry.get('Task');
var boundaryEvent = elementRegistry.get('BoundaryEvent_on_Task');
// when
var canMove = rules.allowed('elements.move', {
shapes: [ task, boundaryEvent ],
hints: {
keyboardMove: true
}
});
// then
expect(canMove).to.be.true;
})
);
});
describe('event attaching', function() {
var testXML = require('./BpmnRules.attaching.bpmn');
beforeEach(bootstrapModeler(testXML, { modules: testModules }));
it('should allow to attach attachable events to SubProcess', inject(function(elementRegistry) {
// given
var attachableEvents = [
'IntermediateThrowEvent',
'MessageCatchEvent',
'TimerCatchEvent',
'SignalCatchEvent',
'ConditionalCatchEvent',
'IntermediateThrowEventWithConnections'
];
var events = attachableEvents.map(function(eventId) {
return elementRegistry.get(eventId);
});
// then
events.forEach(function(event) {
expectCanMove([ event ], 'SubProcess_1', {
attach: 'attach',
move: true
});
});
}));
it('should not allow to attach non-attachable events to SubProcess',
inject(function(elementRegistry) {
// given
var nonAttachableEvents = [
'MessageThrowEvent',
'EscalationThrowEvent',
'LinkCatchEvent',
'LinkThrowEvent',
'CompensateThrowEvent',
'SignalThrowEvent'
];
var events = nonAttachableEvents.map(function(eventId) {
return elementRegistry.get(eventId);
});
// then
events.forEach(function(event) {
expectCanMove([ event ], 'SubProcess_1', {
attach: false,
move: true
});
});
})
);
});
describe('event create', function() {
var testXML = require('../../../fixtures/bpmn/boundary-events.bpmn');
beforeEach(bootstrapModeler(testXML, { modules: testModules }));
it('attach IntermediateEvent to Task', inject(function(elementFactory) {
// given
var eventShape = elementFactory.createShape({
type: 'bpmn:IntermediateThrowEvent',
x: 413, y: 254
});
// then
expectCanMove([ eventShape ], 'Task_1', {
attach: 'attach',
move: false
});
}));
it('not attach IntermediateEvent to CompensationTask', inject(
function(elementFactory) {
// given
var eventShape = elementFactory.createShape({
type: 'bpmn:IntermediateThrowEvent',
x: 413, y: 254
});
// then
expectCanMove([ eventShape ], 'CompensationTask', {
attach: false,
move: false
});
}
));
it('attach IntermediateEvent to SubProcess inner', inject(
function(elementFactory, elementRegistry, bpmnRules) {
// given
var subProcessElement = elementRegistry.get('SubProcess_1');
var eventShape = elementFactory.createShape({
type: 'bpmn:IntermediateThrowEvent',
x: 413, y: 350
});
var position = {
x: subProcessElement.x + subProcessElement.width / 2,
y: subProcessElement.y + subProcessElement.height / 2
};
// when
var canAttach = bpmnRules.canAttach(
[ eventShape ],
subProcessElement,
null,
position
);
// then
expect(canAttach).to.be.false;
}
));
it('attach IntermediateEvent to SubProcess border', inject(
function(elementFactory, elementRegistry, bpmnRules) {
// given
var subProcessElement = elementRegistry.get('SubProcess_1');
var eventShape = elementFactory.createShape({
type: 'bpmn:IntermediateThrowEvent',
x: 0, y: 0
});
var position = {
x: subProcessElement.x + subProcessElement.width / 2,
y: subProcessElement.y + subProcessElement.height
};
// when
var canAttach = bpmnRules.canAttach(
[ eventShape ],
subProcessElement,
null,
position
);
// then
expect(canAttach).to.equal('attach');
}
));
it('not attach IntermediateEvent to compensation activity', inject(
function(elementFactory, elementRegistry, bpmnRules) {
// given
var compensationTask = elementRegistry.get('CompensationTask');
var eventShape = elementFactory.createShape({
type: 'bpmn:IntermediateThrowEvent',
x: 0, y: 0
});
var position = {
x: compensationTask.x + compensationTask.width / 2,
y: compensationTask.y + compensationTask.height
};
// when
var canAttach = bpmnRules.canAttach(
[ eventShape ],
compensationTask,
null,
position
);
// then
expect(canAttach).to.be.false;
}
));
it('not attach IntermediateEvent to ReceiveTask after EventBasedGateway', inject(
function(canvas, modeling, elementFactory, bpmnRules) {
// given
var rootElement = canvas.getRootElement(),
eventBasedGatewayShape = elementFactory.createShape({ type: 'bpmn:EventBasedGateway' }),
receiveTaskShape = elementFactory.createShape({ type: 'bpmn:ReceiveTask' }),
eventShape = elementFactory.createShape({
type: 'bpmn:IntermediateThrowEvent',
x: 0, y: 0
});
var boundaryPosition = {
x: 175,
y: 100 + receiveTaskShape.height
};
// when
modeling.createShape(eventBasedGatewayShape, { x: 100, y: 100 }, rootElement);
modeling.createShape(receiveTaskShape, { x : 150, y: 100 }, rootElement);
modeling.connect(eventBasedGatewayShape, receiveTaskShape, {
type: 'bpmn:SequenceFlow'
});
var canAttach = bpmnRules.canAttach(
[ eventShape ],
receiveTaskShape,
null,
boundaryPosition
);
// then
expect(canAttach).to.be.false;
}
));
it('create IntermediateEvent in SubProcess body', inject(
function(elementFactory, elementRegistry, bpmnRules) {
// given
var subProcessElement = elementRegistry.get('SubProcess_1');
var eventShape = elementFactory.createShape({
type: 'bpmn:IntermediateThrowEvent',
x: 413, y: 250
});
var position = {
x: eventShape.x,
y: eventShape.y
};
// when
var canAttach = bpmnRules.canAttach(
[ eventShape ],
subProcessElement,
null,
position
);
var canCreate = bpmnRules.canCreate(
eventShape,
subProcessElement,
null,
position
);
// then
expect(canAttach).to.be.false;
expect(canCreate).to.be.true;
}
));
});
describe('event append', function() {
var testXML = require('../../../fixtures/bpmn/boundary-events.bpmn');
beforeEach(bootstrapModeler(testXML, { modules: testModules }));
it('append IntermediateEvent from Task', inject(
function(elementFactory, elementRegistry, bpmnRules) {
// given
var subProcessElement = elementRegistry.get('SubProcess_1'),
taskElement = elementRegistry.get('Task_2');
var eventShape = elementFactory.createShape({
type: 'bpmn:IntermediateThrowEvent',
x: 413, y: 250
});
var position = {
x: eventShape.x,
y: eventShape.y
};
// when
var canAttach = bpmnRules.canAttach(
[ eventShape ],
subProcessElement,
taskElement,
position
);
var canCreate = bpmnRules.canCreate(
eventShape,
subProcessElement,
taskElement,
position
);
// then
expect(canAttach).to.be.false;
expect(canCreate).to.be.true;
}
));
it('append IntermediateEvent from BoundaryEvent', inject(
function(elementFactory, elementRegistry, bpmnRules) {
// given
var boundaryElement = elementRegistry.get('BoundaryEvent_1'),
taskElement = elementRegistry.get('Task_2');
var eventShape = elementFactory.createShape({
type: 'bpmn:IntermediateThrowEvent',
x: 413, y: 250
});
// when
var canAttach = bpmnRules.canAttach(
[ eventShape ],
taskElement,
boundaryElement
);
var canCreate = bpmnRules.canCreate(
eventShape,
taskElement,
boundaryElement
);
// then
expect(canAttach).to.eql('attach');
expect(canCreate).to.be.false;
}
));
});
describe('groups', function() {
var testXML = require('./BpmnRules.groups.bpmn');
beforeEach(bootstrapModeler(testXML, { modules: testModules }));
describe('should resize', function() {
it('Group', inject(function(bpmnRules, elementRegistry) {
// given
var groupElement = elementRegistry.get('Group_1');
// when
var canResize = bpmnRules.canResize(groupElement);
// then
expect(canResize).to.be.true;
}));
});
});
describe('lanes', function() {
var testXML = require('./BpmnRules.collaboration-lanes.bpmn');
beforeEach(bootstrapModeler(testXML, { modules: testModules }));
describe('should add', function() {
it('Lane -> Participant', inject(function(elementFactory, elementRegistry, bpmnRules) {
// given
var participantElement = elementRegistry.get('Participant');
var laneShape = elementFactory.createShape({
type: 'bpmn:Lane',
x: 413, y: 250
});
// when
var canCreate = bpmnRules.canCreate(laneShape, participantElement);
// then
expect(canCreate).to.be.true;
}));
it('Lane -> Participant_Lane', inject(function(elementFactory, elementRegistry, bpmnRules) {
// given
var participantElement = elementRegistry.get('Participant_Lane');
var laneShape = elementFactory.createShape({
type: 'bpmn:Lane',
x: 413, y: 250
});
// when
var canCreate = bpmnRules.canCreate(laneShape, participantElement);
// then
expect(canCreate).to.be.true;
}));
it('[not] Lane -> SubProcess', inject(function(elementFactory, elementRegistry, bpmnRules) {
// given
var subProcessElement = elementRegistry.get('SubProcess');
var laneShape = elementFactory.createShape({
type: 'bpmn:Lane',
x: 413, y: 250
});
// when
var canCreate = bpmnRules.canCreate(laneShape, subProcessElement);
// then
expect(canCreate).to.be.false;
}));
});
describe('should not allow move', function() {
it('Lane -> Participant', inject(function(elementFactory, elementRegistry, bpmnRules) {
// given
var participantElement = elementRegistry.get('Participant'),
laneElement = elementRegistry.get('Lane');
// when
var canMove = bpmnRules.canMove([ laneElement ], participantElement);
// then
expect(canMove).to.be.false;
}));
it('Lane -> SubProcess', inject(function(elementFactory, elementRegistry, bpmnRules) {
// given
var subProcessElement = elementRegistry.get('SubProcess'),
laneElement = elementRegistry.get('Lane');
// when
var canMove = bpmnRules.canMove([ laneElement ], subProcessElement);
// then
expect(canMove).to.be.false;
}));
});
describe('should resize', function() {
it('Lane', inject(function(bpmnRules, elementRegistry) {
// given
var laneElement = elementRegistry.get('Lane');
// when
var canResize = bpmnRules.canResize(laneElement);
// then
expect(canResize).to.be.true;
}));
});
describe('should allow drop', function() {
it('SubProcess -> Lane', inject(function(elementFactory, elementRegistry, bpmnRules) {
// given
var element = elementRegistry.get('SubProcess'),
laneElement = elementRegistry.get('Lane');
// when
var canMove = bpmnRules.canMove([ element ], laneElement);
// then
expect(canMove).to.be.true;
}));
it('Task_in_SubProcess -> Lane', inject(function(elementFactory, elementRegistry, bpmnRules) {
// given
var element = elementRegistry.get('Task_in_SubProcess'),
laneElement = elementRegistry.get('Lane');
// when
var canMove = bpmnRules.canMove([ element ], laneElement);
// then
expect(canMove).to.be.true;
}));
});
});
describe('insert', function() {
var testXML = require('./BpmnRules.insert.bpmn');
beforeEach(bootstrapModeler(testXML, { modules: testModules }));
it('insert END -> S1', function() {
expectCanInsert('END', 'S1', false);
});
it('insert START -> S1', function() {
expectCanInsert('START', 'S1', false);
});
it('insert GATEWAY -> S1', function() {
expectCanInsert('GATEWAY', 'S1', true);
});
});
describe('connect on create', function() {
var testXML = require('./BpmnRules.connectOnCreate.bpmn');
beforeEach(bootstrapModeler(testXML, { modules: testModules }));
it('should handle target without parent', inject(function(elementFactory) {
// given
var types = [
'bpmn:Task',
'bpmn:StartEvent',
'bpmn:EndEvent',
'bpmn:Gateway'
];
types.forEach(function(type) {
// when
var element = elementFactory.createShape({ type: type });
// then
expectCanConnect('Task_A', element, {
sequenceFlow: false,
messageFlow: false,
association: false,
dataAssociation: false
});
});
}));
});
describe('data input / output', function() {
describe('in process', function() {
var testXML = require('./BpmnRules.dataInputOutput.process.bpmn');
beforeEach(bootstrapModeler(testXML, { modules: testModules }));
it('should move', inject(function(elementRegistry) {
// when
var elements = [
'Task',
'DataInput',
'DataOutput'
];
// then
expectCanDrop('DataInput', 'Process', true);
expectCanDrop('DataOutput', 'Process', true);
expectCanMove(elements, 'Process', {
attach: false,
move: true
});
}));
});
describe('in collaboration', function() {
var testXML = require('./BpmnRules.dataInputOutput.collaboration.bpmn');
beforeEach(bootstrapModeler(testXML, { modules: testModules }));
it('should move', inject(function(elementRegistry) {
// when
var elements = [
'Task',
'DataInput',
'DataOutput'
];
// then
expectCanDrop('DataInput', 'Participant_A', true);
expectCanDrop('DataInput', 'Participant_B', false);
expectCanDrop('DataOutput', 'Participant_A', true);
expectCanDrop('DataOutput', 'Participant_B', false);
expectCanMove(elements, 'Participant_A', {
attach: false,
move: true
});
expectCanMove(elements, 'Participant_B', {
attach: false,
move: false
});
}));
});
});
describe('integration', function() {
describe('move Lane', function() {
var testXML = require('./BpmnRules.moveLane.bpmn');
beforeEach(bootstrapModeler(testXML, { modules: testModules }));
it('should disallow', inject(function(elementRegistry, rules) {
// given
var lane = elementRegistry.get('Lane_1');
// when
var result = rules.allowed('elements.move', {
shapes: [ lane ]
});
// then
expect(result).to.be.false;
}));
});
});
describe('start connection', function() {
var testXML = require('../../../fixtures/bpmn/simple.bpmn');
beforeEach(bootstrapModeler(testXML, { modules: testModules }));
it('should allow start for given element types', inject(function(elementFactory, rules) {
// given
var types = [
'bpmn:FlowNode',
'bpmn:InteractionNode',
'bpmn:DataObjectReference',
'bpmn:DataStoreReference',
'bpmn:Group',
'bpmn:TextAnnotation'
];
// when
var results = types.map(function(type) {
var element = elementFactory.createShape({ type: type });
return rules.allowed('connection.start', { source: element });
});
// then
results.forEach(function(result) {
expect(result).to.be.true;
});
}));
it('should ignore label elements', inject(function(elementFactory, rules) {
// given
var label = elementFactory.createLabel({});
// when
var result = rules.allowed('connection.start', { source: label });
// then
expect(result).to.be.null;
}));
it('should NOT allow start on unknown element', inject(function(rules) {
// given
var element = { type: 'bpmn:SomeUnknownType' };
// when
var result = rules.allowed('connection.start', { source: element });
// then
expect(result).to.be.false;
}));
});
});
================================================
FILE: test/spec/features/rules/Helper.js
================================================
import {
getBpmnJS
} from 'test/TestHelper';
import {
isArray,
isString,
map
} from 'min-dash';
/**
* @typedef {import('../../../../lib/model/Types').Parent} Parent
* @typedef {import('../../../../lib/model/Types').Shape} Shape
*/
export function expectCanConnect(source, target, rules) {
var results = {};
getBpmnJS().invoke(function(bpmnRules) {
source = get(source);
target = get(target);
if ('sequenceFlow' in rules) {
results.sequenceFlow = bpmnRules.canConnectSequenceFlow(source, target);
}
if ('messageFlow' in rules) {
results.messageFlow = bpmnRules.canConnectMessageFlow(source, target);
}
if ('association' in rules) {
results.association = bpmnRules.canConnectAssociation(source, target);
}
if ('dataAssociation' in rules) {
results.dataAssociation = bpmnRules.canConnectDataAssociation(source, target);
}
if ('compensationAssociation' in rules) {
results.compensationAssociation = bpmnRules.canConnectCompensationAssociation(source, target);
}
});
expect(results).to.eql(rules);
}
export function expectCanDrop(element, target, expectedResult) {
var result = getBpmnJS().invoke(function(bpmnRules) {
return bpmnRules.canDrop(get(element), get(target));
});
expect(result).to.eql(expectedResult);
}
/**
* @param {Shape|Element[]} shape Shape or array of elements to create.
* @param {Parent} target
* @param {any} expectedResult
*/
export function expectCanCreate(shape, target, expectedResult) {
var result = getBpmnJS().invoke(function(rules) {
if (isArray(shape)) {
return rules.allowed('elements.create', {
elements: get(shape),
target: get(target)
});
}
return rules.allowed('shape.create', {
shape: get(shape),
target: get(target)
});
});
expect(result).to.eql(expectedResult);
}
export function expectCanCopy(element, elements, expectedResult) {
var result = getBpmnJS().invoke(function(rules) {
return rules.allowed('element.copy', {
element: element,
elements: elements
});
});
expect(result).to.eql(expectedResult);
}
export function expectCanInsert(element, target, expectedResult) {
var result = getBpmnJS().invoke(function(bpmnRules) {
return bpmnRules.canInsert(get(element), get(target));
});
expect(result).to.eql(expectedResult);
}
export function expectCanMove(elements, target, rules) {
var results = {};
elements = elements.map(get);
getBpmnJS().invoke(function(bpmnRules) {
target = get(target);
if ('attach' in rules) {
results.attach = bpmnRules.canAttach(elements, target);
}
if ('move' in rules) {
results.move = bpmnRules.canMove(elements, target);
}
});
expect(results).to.eql(rules);
}
/**
* Retrieve element, resolving an ID with
* the actual element.
*/
function get(elementId) {
if (isArray(elementId)) {
return map(elementId, get);
}
var element;
if (isString(elementId)) {
element = getBpmnJS().invoke(function(elementRegistry) {
return elementRegistry.get(elementId);
});
if (!element) {
throw new Error('element #' + elementId + ' not found');
}
return element;
}
return elementId;
}
================================================
FILE: test/spec/features/search/BpmnSearchProviderSpec.js
================================================
import {
bootstrapViewer,
inject
} from 'test/TestHelper';
import {
pick
} from 'min-dash';
import coreModule from 'lib/core';
import modelingModule from 'lib/features/modeling';
import bpmnSearchModule from 'lib/features/search';
describe('features - BPMN search provider', function() {
var testModules = [
coreModule,
modelingModule,
bpmnSearchModule
];
describe('collaboration', function() {
var diagramXML = require('./bpmn-search-collaboration.bpmn');
beforeEach(bootstrapViewer(diagramXML, { modules: testModules }));
it('should not return root element (collaboration)', inject(function(bpmnSearch) {
// given
var pattern = 'collaboration';
// when
var elements = bpmnSearch.find(pattern);
// then
expect(elements).to.have.length(0);
}));
});
describe('process', function() {
var diagramXML = require('./bpmn-search.bpmn');
beforeEach(bootstrapViewer(diagramXML, { modules: testModules }));
it('find should return all elements that match label or ID', inject(function(bpmnSearch) {
// given
var pattern = '123456';
// when
var elements = bpmnSearch.find(pattern);
// then
expect(elements).length(3);
elements.forEach(function(e) {
expect(e).to.have.property('element');
expect(e).to.have.property('primaryTokens');
expect(e).to.have.property('secondaryTokens');
});
}));
it('matches IDs', inject(function(bpmnSearch) {
// given
var pattern = 'datastore';
// when
var elements = bpmnSearch.find(pattern);
// then
expectTokens(elements[0].primaryTokens, [
{ value: 'has matched ID' }
]);
expectTokens(elements[0].secondaryTokens, [
{ value: 'some_' },
{ value: 'DataStore', match: true },
{ value: '_123456_id' }
]);
}));
it('should not return root element (process)', inject(function(bpmnSearch) {
// given
var pattern = 'process';
// when
var elements = bpmnSearch.find(pattern);
// then
expect(elements).to.have.length(0);
}));
it('should not return root element (collabsed subprocess)', inject(function(bpmnSearch, elementRegistry) {
// given
var subprocessShape = elementRegistry.get('collapsed');
var pattern = 'Collapsed';
// when
var elements = bpmnSearch.find(pattern);
// then
expect(elements).to.have.length(1);
expect(elements[0].element).to.eql(subprocessShape);
}));
describe('should split result into matched and non matched tokens', function() {
it('matched all', inject(function(bpmnSearch) {
// given
var pattern = 'all matched';
// when
var elements = bpmnSearch.find(pattern);
// then
expectTokens(elements[0].primaryTokens, [
{ value: 'all matched', match: true }
]);
}));
it('matched start', inject(function(bpmnSearch) {
// given
var pattern = 'before';
// when
var elements = bpmnSearch.find(pattern);
// then
expectTokens(elements[0].primaryTokens, [
{ value: 'before', match: true },
{ value: ' 321' }
]);
}));
it('matched middle', inject(function(bpmnSearch) {
// given
var pattern = 'middle';
// when
var elements = bpmnSearch.find(pattern);
// then
expectTokens(elements[0].primaryTokens, [
{ value: '123 ' },
{ value: 'middle', match: true },
{ value: ' 321' }
]);
}));
it('matched end', inject(function(bpmnSearch) {
// given
var pattern = 'after';
// when
var elements = bpmnSearch.find(pattern);
// then
expectTokens(elements[0].primaryTokens, [
{ value: '123 ' },
{ value: 'after', match: true }
]);
}));
});
});
describe('sorting', function() {
var diagramXML = require('./bpmn-search-sorting.bpmn');
beforeEach(bootstrapViewer(diagramXML, { modules: testModules }));
it('should sort', inject(function(bpmnSearch) {
// given
var pattern = 'foo';
// when
var elements = bpmnSearch.find(pattern);
var idsAndNames = elements.map(e => [ e.element.id, e.element.businessObject.name ]);
// then
expect(idsAndNames).to.eql([
[ 'foo_2', 'foo bar' ],
[ 'foo_3', 'foo bar' ],
[ 'bar', 'foo bar' ],
[ 'foo_0', 'bar' ],
[ 'foo_1', 'baz' ],
[ 'baz', 'bar foo' ]
]);
}));
it('should handle elements without label', inject(function(bpmnSearch) {
// given
var pattern = 'ass';
// when
var elements = bpmnSearch.find(pattern);
// then
expect(elements).length(2);
expect(elements[0].element.id).to.eql('Association_1');
expect(elements[1].element.id).to.eql('Association_2');
}));
});
});
// helpers ///////////////
function expectTokens(tokens, expectedTokens) {
const cleanTokens = tokens.map(
token => pick(token, [ 'value', 'match' ])
);
expect(cleanTokens).to.eql(expectedTokens);
}
================================================
FILE: test/spec/features/search/bpmn-search-collaboration.bpmn
================================================
SequenceFlow_0wgiusn
some_DataStore_123456_id
SequenceFlow_0wgiusn
SequenceFlow_1bhe9h2
SequenceFlow_02ymelh
SequenceFlow_02ymelh
SequenceFlow_0ugwp0d
SequenceFlow_0ugwp0d
SequenceFlow_1bhe9h2
================================================
FILE: test/spec/features/search/bpmn-search-sorting.bpmn
================================================
================================================
FILE: test/spec/features/search/bpmn-search.bpmn
================================================
SequenceFlow_0wgiusn
some_DataStore_123456_id
SequenceFlow_0wgiusn
SequenceFlow_1bhe9h2
SequenceFlow_02ymelh
SequenceFlow_02ymelh
SequenceFlow_0ugwp0d
SequenceFlow_0ugwp0d
SequenceFlow_1bhe9h2
================================================
FILE: test/spec/features/snapping/BpmnConnectSnapping.bpmn
================================================
================================================
FILE: test/spec/features/snapping/BpmnConnectSnappingSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import connectModule from 'diagram-js/lib/features/connect';
import coreModule from 'lib/core';
import globalConnectModule from 'diagram-js/lib/features/global-connect';
import modelingModule from 'lib/features/modeling';
import rulesModule from 'lib/features/rules';
import snappingModule from 'lib/features/snapping';
import { createCanvasEvent as canvasEvent } from '../../../util/MockEvents';
describe('features/snapping - BpmnConnectSnapping', function() {
var testModules = [
connectModule,
coreModule,
globalConnectModule,
modelingModule,
rulesModule,
snappingModule
];
var diagramXML = require('./BpmnConnectSnapping.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
beforeEach(inject(function(dragging) {
dragging.setOptions({ manual: true });
}));
describe('sequence flow', function() {
describe('boundary event loop', function() {
it('should snap left', inject(
function(connect, dragging, elementRegistry) {
// given
var boundaryEvent = elementRegistry.get('BoundaryEvent_Bottom'),
subProcess = elementRegistry.get('SubProcess'),
subProcessGfx = elementRegistry.getGraphics(subProcess);
// when
connect.start(canvasEvent({ x: 590, y: 200 }), boundaryEvent);
dragging.hover({ element: subProcess, gfx: subProcessGfx });
dragging.move(canvasEvent({ x: 400, y: 115 }));
dragging.end();
// then
var waypoints = boundaryEvent.outgoing[0].waypoints;
expect(waypoints).to.have.length(5);
expect(waypoints[ 4 ].original).to.eql({
x: 420,
y: 115
});
}
));
it('should snap bottom (from bottom)', inject(
function(connect, dragging, elementRegistry) {
// given
var boundaryEvent = elementRegistry.get('BoundaryEvent_Bottom'),
subProcess = elementRegistry.get('SubProcess'),
subProcessGfx = elementRegistry.getGraphics(subProcess);
// when
connect.start(canvasEvent({ x: 630, y: 200 }), boundaryEvent);
dragging.hover({ element: subProcess, gfx: subProcessGfx });
dragging.move(canvasEvent({ x: 580, y: 115 }));
dragging.end();
// then
var waypoints = boundaryEvent.outgoing[0].waypoints;
expect(waypoints).to.have.length(4);
expect(waypoints[ 3 ].original).to.eql({
x: 550,
y: 115
});
}
));
it('should snap right', inject(
function(connect, dragging, elementRegistry) {
// given
var boundaryEvent = elementRegistry.get('BoundaryEvent_Right'),
subProcess = elementRegistry.get('SubProcess'),
subProcessGfx = elementRegistry.getGraphics(subProcess);
// when
connect.start(canvasEvent({ x: 760, y: 130 }), boundaryEvent);
dragging.hover({ element: subProcess, gfx: subProcessGfx });
dragging.move(canvasEvent({ x: 580, y: 115 }));
dragging.end();
// then
var waypoints = boundaryEvent.outgoing[0].waypoints;
expect(waypoints).to.have.length(4);
expect(waypoints[ 3 ].original).to.eql({
x: 580,
y: 90
});
}
));
it('should snap bottom (from right)', inject(
function(connect, dragging, elementRegistry) {
// given
var boundaryEvent = elementRegistry.get('BoundaryEvent_Right'),
subProcess = elementRegistry.get('SubProcess'),
subProcessGfx = elementRegistry.getGraphics(subProcess);
// when
connect.start(canvasEvent({ x: 760, y: 130 }), boundaryEvent);
dragging.hover({ element: subProcess, gfx: subProcessGfx });
dragging.move(canvasEvent({ x: 580, y: 200 }));
dragging.end();
// then
var waypoints = boundaryEvent.outgoing[0].waypoints;
expect(waypoints).to.have.length(5);
expect(waypoints[ 4 ].original).to.eql({
x: 580,
y: 180
});
}
));
});
describe('activity target', function() {
it('should snap to task mid', inject(
function(connect, dragging, elementRegistry) {
// given
var startEvent = elementRegistry.get('StartEvent_2'),
task = elementRegistry.get('Task_3'),
taskGfx = elementRegistry.getGraphics(task);
// when
connect.start(canvasEvent({ x: 80, y: 845 }), startEvent);
dragging.hover({ element: task, gfx: taskGfx });
dragging.move(canvasEvent({ x: 200, y: 850 }));
dragging.end();
// then
var waypoints = startEvent.outgoing[0].waypoints;
expect(waypoints).to.have.length(2);
expect(waypoints[ 1 ].original).to.eql({
x: 200,
y: 845
});
}
));
it('should snap to sub-process mid', inject(
function(connect, dragging, elementRegistry) {
// given
var startEvent = elementRegistry.get('StartEvent_3'),
subProcess = elementRegistry.get('SubProcess_1'),
subProcessGfx = elementRegistry.getGraphics(subProcess);
// when
connect.start(canvasEvent({ x: 80, y: 1025 }), startEvent);
dragging.hover({ element: subProcess, gfx: subProcessGfx });
dragging.move(canvasEvent({ x: 325, y: 1030 }));
dragging.end();
// then
var waypoints = startEvent.outgoing[0].waypoints;
expect(waypoints).to.have.length(2);
expect(waypoints[ 1 ].original).to.eql({
x: 325,
y: 1025
});
}
));
});
it('should to snap gateway target mid', inject(
function(connect, dragging, elementRegistry) {
// given
var startEvent = elementRegistry.get('StartEvent_1'),
gateway = elementRegistry.get('Gateway_1'),
gatewayGfx = elementRegistry.getGraphics(gateway);
// when
connect.start(canvasEvent({ x: 80, y: 50 }), startEvent);
dragging.hover({ element: gateway, gfx: gatewayGfx });
dragging.move(canvasEvent({ x: 255, y: 55 }));
dragging.end();
// then
var waypoints = startEvent.outgoing[0].waypoints;
expect(waypoints).to.have.length(2);
expect(waypoints[ 1 ].original).to.eql({
x: 250,
y: 50
});
}
));
it('should snap to event target mid', inject(
function(connect, dragging, elementRegistry) {
// given
var startEvent = elementRegistry.get('StartEvent_1'),
endEvent = elementRegistry.get('EndEvent_1'),
endEventGfx = elementRegistry.getGraphics(endEvent);
// when
connect.start(canvasEvent({ x: 80, y: 50 }), startEvent);
dragging.hover({ element: endEvent, gfx: endEventGfx });
dragging.move(canvasEvent({ x: 85, y: 245 }));
dragging.end();
// then
var waypoints = startEvent.outgoing[0].waypoints;
expect(waypoints).to.have.length(2);
expect(waypoints[ 1 ].original).to.eql({
x: 80,
y: 240
});
}
));
});
describe('message flow', function() {
describe('connect', function() {
it('should snap target', inject(function(connect, dragging, elementRegistry) {
// given
var task = elementRegistry.get('Task_1'),
intermediateCatchEvent = elementRegistry.get('IntermediateCatchEvent_1'),
intermediateCatchEventGfx = elementRegistry.getGraphics(intermediateCatchEvent);
// when
connect.start(canvasEvent({ x: 250, y: 240 }), task);
dragging.hover({ element: intermediateCatchEvent, gfx: intermediateCatchEventGfx });
dragging.move(canvasEvent({ x: 185, y: 555 }));
dragging.end();
// then
var waypoints = task.outgoing[0].waypoints;
expect(waypoints).to.have.length(4);
expect(waypoints[ 3 ].original).to.eql({
x: 180,
y: 550
});
}));
});
describe('global connect', function() {
it('should snap source', inject(function(connect, dragging, elementRegistry) {
// given
var intermediateThrowEvent = elementRegistry.get('IntermediateThrowEvent_1'),
task = elementRegistry.get('Task_1'),
taskGfx = elementRegistry.getGraphics(task);
// when
connect.start(null, intermediateThrowEvent, { x: 75, y: 555 });
dragging.hover({ element: task, gfx: taskGfx });
dragging.move(canvasEvent({ x: 290, y: 240 }));
dragging.end();
// then
var waypoints = intermediateThrowEvent.outgoing[0].waypoints;
expect(waypoints).to.have.length(4);
expect(waypoints[ 0 ].original).to.eql({
x: 70,
y: 550
});
// NOT snapped
expect(waypoints[ 3 ].original).to.eql({
x: 290,
y: 240
});
}));
it('should snap target', inject(function(connect, dragging, elementRegistry) {
// given
var task = elementRegistry.get('Task_1'),
intermediateCatchEvent = elementRegistry.get('IntermediateCatchEvent_1'),
intermediateCatchEventGfx = elementRegistry.getGraphics(intermediateCatchEvent);
// when
connect.start(null, task, { x: 255, y: 245 });
dragging.hover({ element: intermediateCatchEvent, gfx: intermediateCatchEventGfx });
dragging.move(canvasEvent({ x: 185, y: 555 }));
dragging.end();
// then
var waypoints = task.outgoing[0].waypoints;
expect(waypoints).to.have.length(4);
// NOT snapped
expect(waypoints[ 0 ].original).to.eql({
x: 255,
y: 245
});
expect(waypoints[ 3 ].original).to.eql({
x: 180,
y: 550
});
}));
});
});
});
================================================
FILE: test/spec/features/snapping/BpmnCreateMoveSnapping.boundary-events.bpmn
================================================
================================================
FILE: test/spec/features/snapping/BpmnCreateMoveSnapping.collaboration.bpmn
================================================
Task_1
================================================
FILE: test/spec/features/snapping/BpmnCreateMoveSnapping.docking-create-mode.bpmn
================================================
SequenceFlow_1
SequenceFlow_1
================================================
FILE: test/spec/features/snapping/BpmnCreateMoveSnapping.docking-points.bpmn
================================================
================================================
FILE: test/spec/features/snapping/BpmnCreateMoveSnapping.process.bpmn
================================================
================================================
FILE: test/spec/features/snapping/BpmnCreateMoveSnapping.sequence-flows.bpmn
================================================
SequenceFlow_1
SequenceFlow_1
================================================
FILE: test/spec/features/snapping/BpmnCreateMoveSnapping.trbl-snapping.bpmn
================================================
TEXT_1
TEXT_2
================================================
FILE: test/spec/features/snapping/BpmnCreateMoveSnappingSpec.js
================================================
import {
bootstrapModeler,
getBpmnJS,
inject
} from 'test/TestHelper';
import TestContainer from 'mocha-test-container-support';
import coreModule from 'lib/core';
import createModule from 'diagram-js/lib/features/create';
import modelingModule from 'lib/features/modeling';
import moveModule from 'diagram-js/lib/features/move';
import rulesModule from 'lib/features/rules';
import snappingModule from 'lib/features/snapping';
import {
isSnapped,
mid
} from 'diagram-js/lib/features/snapping/SnapUtil';
import { createCanvasEvent as canvasEvent } from '../../../util/MockEvents';
import {
DEFAULT_LABEL_SIZE,
getExternalLabelMid
} from 'lib/util/LabelUtil';
import { queryAll as domQueryAll } from 'min-dom';
import { attr as svgAttr } from 'tiny-svg';
describe('features/snapping - BpmnCreateMoveSnapping', function() {
var testModules = [
coreModule,
createModule,
modelingModule,
moveModule,
rulesModule,
snappingModule,
{
__init__: [ function(dragging) {
dragging.setOptions({ manual: true });
} ]
}
];
describe('create participant', function() {
describe('process', function() {
var diagramXML = require('./BpmnCreateMoveSnapping.process.bpmn');
it('should snap participant if constrained', function(done) {
bootstrapModeler(diagramXML, {
container: TestContainer.get(this),
modules: testModules
})().then(function() {
// when
inject(function(canvas, create, dragging, elementFactory, eventBus) {
// given
dragging.setOptions({ manual: true });
var participantShape = elementFactory.createParticipantShape(false),
rootElement = canvas.getRootElement(),
rootGfx = canvas.getGraphics(rootElement);
create.start(canvasEvent({ x: 0, y: 0 }), participantShape);
dragging.hover({ element: rootElement, gfx: rootGfx });
eventBus.once('create.move', function(event) {
// then
// expect snapped to avoid snapping outside of constraints
expect(isSnapped(event)).to.be.true;
done();
});
// when
dragging.move(canvasEvent({ x: 1000, y: 1000 }));
})();
});
});
});
describe('collaboration', function() {
var diagramXML = require('./BpmnCreateMoveSnapping.collaboration.bpmn');
it('should snap to participant border with higher priority', function(done) {
var container = TestContainer.get(this);
bootstrapModeler(diagramXML, {
container: container,
modules: testModules
})().then(function() {
// when
inject(function(create, dragging, elementFactory, elementRegistry, eventBus) {
// given
dragging.setOptions({ manual: true });
var participant = elementFactory.createParticipantShape(false),
collaboration = elementRegistry.get('Collaboration_1'),
collaborationGfx = elementRegistry.getGraphics(collaboration);
create.start(canvasEvent({ x: 0, y: 0 }), participant);
dragging.hover({ element: collaboration, gfx: collaborationGfx });
dragging.move(canvasEventTopLeft({ x: 0, y: 0 }, participant));
eventBus.once('create.move', function(event) {
// then
// expect snap line at left border of participant
expect(svgAttr(domQueryAll('.djs-snap-line', container)[1], 'd'))
.to.equal('M 100,-100000 L 100, +100000');
done();
});
// when
dragging.move(canvasEventTopLeft({ x: 95, y: 400 }, participant));
})();
});
});
});
});
describe('boundary events', function() {
describe('creating boundary event', function() {
var diagramXML = require('./BpmnCreateMoveSnapping.process.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
var task, taskGfx, intermediateThrowEvent;
describe('without label', function() {
beforeEach(inject(function(create, dragging, elementRegistry, elementFactory) {
task = elementRegistry.get('Task_1');
taskGfx = elementRegistry.getGraphics(task);
intermediateThrowEvent = elementFactory.createShape({
type: 'bpmn:IntermediateThrowEvent'
});
create.start(canvasEvent({ x: 0, y: 0 }), intermediateThrowEvent);
dragging.hover({ element: task, gfx: taskGfx });
}));
it('should snap to top', inject(function(dragging) {
// when
dragging.move(canvasEvent({ x: 150, y: 95 }));
dragging.end();
// then
var boundaryEvent = getBoundaryEvent(task);
expect(mid(boundaryEvent)).to.eql({
x: 150,
y: 100 // 95 snapped to 100
});
}));
it('should snap to right', inject(function(dragging) {
// when
dragging.move(canvasEvent({ x: 195, y: 140 }));
dragging.end();
// then
var boundaryEvent = getBoundaryEvent(task);
expect(mid(boundaryEvent)).to.eql({
x: 200, // 195 snapped to 200
y: 140
});
}));
it('should snap to bottom', inject(function(dragging) {
// when
dragging.move(canvasEvent({ x: 150, y: 175 }));
dragging.end();
// then
var boundaryEvent = getBoundaryEvent(task);
expect(mid(boundaryEvent)).to.eql({
x: 150,
y: 180 // 175 snapped to 180
});
}));
it('should snap to left', inject(function(dragging) {
// when
dragging.move(canvasEvent({ x: 95, y: 140 }));
dragging.end();
// then
var boundaryEvent = getBoundaryEvent(task);
expect(mid(boundaryEvent)).to.eql({
x: 100, // 95 snapped to 100
y: 140
});
}));
});
describe('with label', function() {
beforeEach(inject(function(
bpmnFactory,
create,
dragging,
elementFactory,
elementRegistry,
textRenderer
) {
task = elementRegistry.get('Task_1');
taskGfx = elementRegistry.getGraphics(task);
intermediateThrowEvent = elementFactory.createShape({
businessObject: bpmnFactory.create('bpmn:IntermediateThrowEvent', {
name: 'Foo'
}),
type: 'bpmn:IntermediateThrowEvent',
x: 0,
y: 0
});
var externalLabelMid = getExternalLabelMid(intermediateThrowEvent);
var externalLabelBounds = textRenderer.getExternalLabelBounds(DEFAULT_LABEL_SIZE, 'Foo');
var label = elementFactory.createLabel({
labelTarget: intermediateThrowEvent,
x: externalLabelMid.x - externalLabelBounds.width / 2,
y: externalLabelMid.y - externalLabelBounds.height / 2,
width: externalLabelBounds.width,
height: externalLabelBounds.height,
businessObject: intermediateThrowEvent.businessObject
});
create.start(canvasEvent({ x: 0, y: 0 }), [ intermediateThrowEvent, label ]);
dragging.hover({ element: task, gfx: taskGfx });
}));
it('should snap to top-left', inject(function(dragging) {
// when
dragging.move(canvasEvent({ x: 90, y: 95 }));
dragging.end();
// then
var boundaryEvent = getBoundaryEvent(task);
expect(mid(boundaryEvent)).to.eql({
x: 100, // 90 snapped to 100
y: 100 // 95 snapped to 100
});
}));
it('should snap to top-right', inject(function(dragging) {
// when
dragging.move(canvasEvent({ x: 210, y: 95 }));
dragging.end();
// then
var boundaryEvent = getBoundaryEvent(task);
expect(mid(boundaryEvent)).to.eql({
x: 200, // 210 snapped to 200
y: 100 // 95 snapped to 100
});
}));
it('should snap to bottom-left', inject(function(dragging) {
// when
dragging.move(canvasEvent({ x: 90, y: 190 }));
dragging.end();
// then
var boundaryEvent = getBoundaryEvent(task);
expect(mid(boundaryEvent)).to.eql({
x: 100, // 90 snapped to 100
y: 180 // 190 snapped to 180
});
}));
it('should snap to bottom-right', inject(function(dragging) {
// when
dragging.move(canvasEvent({ x: 210, y: 190 }));
dragging.end();
// then
var boundaryEvent = getBoundaryEvent(task);
expect(mid(boundaryEvent)).to.eql({
x: 200, // 210 snapped to 200
y: 180 // 190 snapped to 180
});
}));
});
});
describe('snapping to boundary events', function() {
var diagramXML = require('./BpmnCreateMoveSnapping.boundary-events.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
var task;
beforeEach(inject(function(dragging, elementRegistry, move) {
task = elementRegistry.get('Task_1');
var process = elementRegistry.get('Process_1'),
processGfx = elementRegistry.getGraphics(process);
move.start(canvasEventTopLeft({ x: 100, y: 400 }, task), task, true);
dragging.hover({ element: process, gfx: processGfx });
dragging.move(canvasEventTopLeft({ x: 100, y: 400 }, task));
}));
it('should snap to boundary events', inject(function(dragging) {
// when
dragging.move(canvasEventTopLeft({ x: 245, y: 400 }, task));
dragging.end();
// then
expect(task).to.have.bounds({
x: 250, // 245 snapped to 250
y: 400,
width: 100,
height: 80
});
}));
});
});
describe('sequence flows', function() {
var diagramXML = require('./BpmnCreateMoveSnapping.sequence-flows.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
var sequenceFlow, sequenceFlowGfx, task;
beforeEach(inject(function(create, dragging, elementRegistry, elementFactory) {
sequenceFlow = elementRegistry.get('SequenceFlow_1');
sequenceFlowGfx = elementRegistry.getGraphics(sequenceFlow);
task = elementFactory.createShape({
type: 'bpmn:Task'
});
create.start(canvasEvent({ x: 0, y: 0 }), task);
dragging.hover({ element: sequenceFlow, gfx: sequenceFlowGfx });
}));
it('should add snap targets of sequence flow parent', inject(function(dragging) {
// when
dragging.move(canvasEventTopLeft({ x: 195, y: 60 }, task));
dragging.end();
// then
expect(task).to.have.bounds({
x: 200, // 195 snapped to 200
y: 60,
width: 100,
height: 80
});
}));
});
describe('lanes', function() {
var diagramXML = require('./BpmnCreateMoveSnapping.collaboration.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
var task;
beforeEach(inject(function(dragging, elementRegistry, move) {
task = elementRegistry.get('Task_1');
move.start(canvasEvent({ x: 200, y: 165 }), task);
}));
it('should should NOT snap to lanes', inject(function(dragging) {
// when
// lane mid is { x: 415, y: 162.5 }
dragging.move(canvasEvent({ x: 410, y: 160 }));
dragging.end();
// then
expect(task).to.have.bounds({
x: 360,
y: 120,
width: 100,
height: 80
});
}));
});
describe('docking points', function() {
describe('move mode', function() {
var diagramXML = require('./BpmnCreateMoveSnapping.docking-points.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
var participant,
participantGfx;
beforeEach(inject(function(dragging, elementRegistry, move) {
participant = elementRegistry.get('Participant_2');
participantGfx = elementRegistry.getGraphics(participant);
}));
it('should snap to docking point (incoming connections)', inject(
function(dragging, elementRegistry, move) {
// given
var task = elementRegistry.get('Task_2');
move.start(canvasEvent({ x: 400, y: 540 }), task);
dragging.hover({ element: participant, gfx: participantGfx });
dragging.move(canvasEvent({ x: 0, y: 0 }));
// when
dragging.move(canvasEvent({ x: 270, y: 540 }));
dragging.end();
// then
expect(mid(task)).to.eql({
x: 275,
y: 540
});
}
));
it('should snap to docking point (outgoing connections)', inject(
function(dragging, elementRegistry, move) {
// given
var task = elementRegistry.get('Task_4');
move.start(canvasEvent({ x: 600, y: 540 }), task);
dragging.hover({ element: participant, gfx: participantGfx });
dragging.move(canvasEvent({ x: 0, y: 0 }));
// when
dragging.move(canvasEvent({ x: 475, y: 540 }));
dragging.end();
// then
expect(mid(task)).to.eql({
x: 480,
y: 540
});
}
));
});
describe('create mode', function() {
var diagramXML = require('./BpmnCreateMoveSnapping.docking-create-mode.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
it('should correctly set snap origins', inject(
function(elementRegistry, copyPaste, eventBus) {
// given
var task1 = elementRegistry.get('Task_1');
var startListener = sinon.spy(function(event) {
var snapContext = event.context.snapContext;
var snapLocations = snapContext.getSnapLocations();
var sequenceFlowSnapOrigin = snapContext.getSnapOrigin(snapLocations[3]);
// then
expect(sequenceFlowSnapOrigin.x).to.be.eql(-30);
expect(sequenceFlowSnapOrigin.y).to.be.eql(-10);
});
eventBus.on('create.start', startListener);
// when
copyPaste.copy(task1);
copyPaste.paste();
// then
expect(startListener).to.have.been.called;
}
));
});
});
describe('TRBL snapping', function() {
var diagramXML = require('./BpmnCreateMoveSnapping.trbl-snapping.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
function get(element) {
return getBpmnJS().invoke(function(elementRegistry) {
return elementRegistry.get(element);
});
}
function absoluteMove(element, toPosition) {
getBpmnJS().invoke(function(elementRegistry, move, dragging, canvas) {
var parent = element.parent;
move.start(canvasEvent({ x: 0, y: 0 }), element);
dragging.hover({
element: parent,
gfx: canvas.getGraphics(parent)
});
dragging.move(canvasEvent({ x: 100, y: 100 }), element);
dragging.move(canvasEvent({
x: toPosition.x - element.x,
y: toPosition.y - element.y
}));
dragging.end();
});
}
it('should snap text annotations', function() {
// given
var annotation = get('TEXT_1');
var otherAnnotation = get('TEXT_2');
// when
absoluteMove(annotation, {
x: otherAnnotation.x + 5,
y: otherAnnotation.y - 5
});
// then
expect(annotation).to.have.position(otherAnnotation);
});
it('should snap task to container', function() {
// given
var task = get('TASK');
var subProcess = get('SUB_PROCESS_1');
// when
absoluteMove(task, {
x: subProcess.x,
y: subProcess.y - 5
});
// then
expect(task).to.have.position(subProcess);
});
it('should snap container to container', function() {
// given
var participant = get('PARTICIPANT_1');
var otherParticipant = get('PARTICIPANT_2');
// when
absoluteMove(participant, {
x: otherParticipant.x + 5,
y: otherParticipant.y
});
// then
expect(participant).to.have.position(otherParticipant);
});
it('should snap container to container right', function() {
// given
var participant = get('PARTICIPANT_1');
var otherParticipant = get('PARTICIPANT_2');
// when
absoluteMove(participant, {
x: otherParticipant.x + otherParticipant.width - participant.width + 5,
y: 5
});
// then
expect(participant).to.have.position({
x: otherParticipant.x + otherParticipant.width - participant.width,
y: 5
});
});
});
});
// helpers //////////
function canvasEventTopLeft(position, shape) {
return canvasEvent({
x: position.x + shape.width / 2,
y: position.y + shape.height / 2
});
}
function getBoundaryEvent(element) {
return element.attachers[0];
}
================================================
FILE: test/spec/features/space-tool/BpmnSpaceTool.artifacts.bpmn
================================================
ANNOTATION_1
ANNOTATION_3
ANNOTATION_2
ANNOTATION_4
ANNOTATION_5
================================================
FILE: test/spec/features/space-tool/BpmnSpaceTool.basics.bpmn
================================================
SequenceFlow_1
SequenceFlow_2
SequenceFlow_3
SequenceFlow_3
SequenceFlow_4
SequenceFlow_4
SequenceFlow_1
SequenceFlow_2
================================================
FILE: test/spec/features/space-tool/BpmnSpaceTool.boundary-events.bpmn
================================================
================================================
FILE: test/spec/features/space-tool/BpmnSpaceTool.participants.bpmn
================================================
================================================
FILE: test/spec/features/space-tool/BpmnSpaceTool.text-annotations.bpmn
================================================
Foo bar baz
================================================
FILE: test/spec/features/space-tool/BpmnSpaceToolSpec.js
================================================
import {
bootstrapModeler,
getBpmnJS,
inject
} from 'test/TestHelper';
import coreModule from 'lib/core';
import modelingModule from 'lib/features/modeling';
import rulesModule from 'lib/features/rules';
import snappingModule from 'lib/features/snapping';
import spaceToolModule from 'lib/features/space-tool';
import { createCanvasEvent as canvasEvent } from '../../../util/MockEvents';
import { getMid } from 'diagram-js/lib/layout/LayoutUtil';
import { isString, pick } from 'min-dash';
import { isMac } from 'diagram-js/lib/util/Platform';
var invertModifier = isMac() ? { metaKey: true } : { ctrlKey: true };
describe('features/space-tool - BpmnSpaceTool', function() {
// adopt conservative retry strategy
// in an attempt to improve the stability
// of our test suite
this.retries(2);
var testModules = [
coreModule,
modelingModule,
rulesModule,
snappingModule,
spaceToolModule
];
describe('basics', function() {
var diagramXML = require('./BpmnSpaceTool.basics.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
beforeEach(inject(function(dragging) {
dragging.setOptions({ manual: true });
}));
describe('add space', function() {
it('should add space top', inject(function() {
// given
var subProcess1 = $element('SubProcess_1');
var subProcess1Bounds = getBounds(subProcess1);
// when
makeSpace({ x: 420, y: 240 }, { dy: -100 }, true);
// then
expect(subProcess1).to.have.bounds({
x: subProcess1Bounds.x,
y: subProcess1Bounds.y - 100,
width: subProcess1Bounds.width,
height: subProcess1Bounds.height + 100
});
}));
it('should add space right', inject(function() {
// given
var endEvent1 = $element('EndEvent_1'),
endEvent2 = $element('EndEvent_2'),
endEvent1Label = endEvent1.label,
endEvent2Label = endEvent2.label,
sequenceFlow2 = $element('SequenceFlow_2'),
sequenceFlow4 = $element('SequenceFlow_4'),
subProcess1 = $element('SubProcess_1');
var endEvent1Mid = getMid(endEvent1),
endEvent2Mid = getMid(endEvent2),
endEvent1LabelMid = getMid(endEvent1Label),
endEvent2LabelMid = getMid(endEvent2Label),
sequenceFlow2Waypoints = sequenceFlow2.waypoints.slice(),
sequenceFlow4Waypoints = sequenceFlow4.waypoints.slice(),
subProcess1Bounds = getBounds(subProcess1);
// when
makeSpace({ x: 420, y: 240 }, { dx: 100 });
// then
expect(getMid(endEvent1)).to.eql({
x: endEvent1Mid.x + 100,
y: endEvent1Mid.y
});
expect(getMid(endEvent2)).to.eql({
x: endEvent2Mid.x + 100,
y: endEvent2Mid.y
});
expect(getMid(endEvent1Label)).to.eql({
x: endEvent1LabelMid.x + 100,
y: endEvent1LabelMid.y
});
expect(getMid(endEvent2Label).x).to.be.closeTo(650, 1); // Label position was adjusted
expect(getMid(endEvent2Label).y).to.equal(endEvent2LabelMid.y);
expect(sequenceFlow2.waypoints[ 0 ]).to.include({ x: sequenceFlow2Waypoints[ 0 ].x + 100, y: 240 });
expect(sequenceFlow2.waypoints[ 1 ]).to.include({ x: sequenceFlow2Waypoints[ 1 ].x + 100, y: 240 });
expect(sequenceFlow4.waypoints[ 0 ]).to.include({ x: sequenceFlow4Waypoints[ 0 ].x, y: 240 });
expect(sequenceFlow4.waypoints[ 1 ]).to.include({ x: sequenceFlow4Waypoints[ 1 ].x + 100, y: 240 });
expect(subProcess1).to.have.bounds({
x: subProcess1Bounds.x,
y: subProcess1Bounds.y,
width: subProcess1Bounds.width + 100,
height: subProcess1Bounds.height
});
}));
it('should add space bottom', inject(function() {
// given
var subProcess1 = $element('SubProcess_1');
var subProcess1Bounds = getBounds(subProcess1);
// when
makeSpace({ x: 420, y: 240 }, { dy: 100 });
// then
expect(subProcess1).to.have.bounds({
x: subProcess1Bounds.x,
y: subProcess1Bounds.y,
width: subProcess1Bounds.width,
height: subProcess1Bounds.height + 100
});
}));
it('should add space left', inject(function() {
// given
var startEvent1 = $element('StartEvent_1'),
startEvent2 = $element('StartEvent_2'),
startEvent1Label = startEvent1.label,
startEvent2Label = startEvent2.label,
sequenceFlow1 = $element('SequenceFlow_1'),
sequenceFlow3 = $element('SequenceFlow_3'),
subProcess1 = $element('SubProcess_1');
var startEvent1Mid = getMid(startEvent1),
startEvent2Mid = getMid(startEvent2),
startEvent1LabelMid = getMid(startEvent1Label),
startEvent2LabelMid = getMid(startEvent2Label),
sequenceFlow1Waypoints = sequenceFlow1.waypoints.slice(),
sequenceFlow3Waypoints = sequenceFlow3.waypoints.slice(),
subProcess1Bounds = getBounds(subProcess1);
// when
makeSpace({ x: 420, y: 240 }, { dx: -100 }, true);
// then
expect(getMid(startEvent1)).to.eql({
x: startEvent1Mid.x - 100,
y: startEvent1Mid.y
});
expect(getMid(startEvent2)).to.eql({
x: startEvent2Mid.x - 100,
y: startEvent2Mid.y
});
expect(getMid(startEvent1Label)).to.eql({
x: startEvent1LabelMid.x - 100,
y: startEvent1LabelMid.y
});
expect(getMid(startEvent2Label).x).to.be.closeTo(198, 1); // Label position was adjusted
expect(getMid(startEvent2Label).y).to.equal(startEvent2LabelMid.y);
expect(sequenceFlow1.waypoints[ 0 ]).to.include({ x: sequenceFlow1Waypoints[ 0 ].x - 100, y: 240 });
expect(sequenceFlow1.waypoints[ 1 ]).to.include({ x: sequenceFlow1Waypoints[ 1 ].x - 100, y: 240 });
expect(sequenceFlow3.waypoints[ 0 ]).to.include({ x: sequenceFlow3Waypoints[ 0 ].x - 100, y: 240 });
expect(sequenceFlow3.waypoints[ 1 ]).to.include({ x: sequenceFlow3Waypoints[ 1 ].x, y: 240 });
expect(subProcess1).to.have.bounds({
x: subProcess1Bounds.x - 100,
y: subProcess1Bounds.y,
width: subProcess1Bounds.width + 100,
height: subProcess1Bounds.height
});
}));
});
describe('remove', function() {
it('should remove space top', inject(function() {
// given
var subProcess1 = $element('SubProcess_1');
var subProcess1Bounds = getBounds(subProcess1);
// when
makeSpace({ x: 420, y: 240 }, { dy: 100 }, true);
// then
expect(subProcess1).to.have.bounds({
x: subProcess1Bounds.x,
y: 180,
width: subProcess1Bounds.width,
height: 160
});
}));
it('should remove space right', inject(function() {
// given
var endEvent1 = $element('EndEvent_1'),
endEvent2 = $element('EndEvent_2'),
endEvent1Label = endEvent1.label,
endEvent2Label = endEvent2.label,
sequenceFlow2 = $element('SequenceFlow_2'),
sequenceFlow4 = $element('SequenceFlow_4'),
subProcess1 = $element('SubProcess_1');
var endEvent1Mid = getMid(endEvent1),
endEvent2Mid = getMid(endEvent2),
endEvent1LabelMid = getMid(endEvent1Label),
sequenceFlow2Waypoints = sequenceFlow2.waypoints.slice(),
subProcess1Bounds = getBounds(subProcess1);
// when
makeSpace({ x: 420, y: 240 }, { dx: -100 });
// then
expect(getMid(endEvent1)).to.eql({
x: endEvent1Mid.x - 100,
y: endEvent1Mid.y
});
expect(getMid(endEvent2)).to.eql({
x: endEvent2Mid.x - 100,
y: endEvent2Mid.y
});
expect(getMid(endEvent1Label)).to.eql({
x: endEvent1LabelMid.x - 100,
y: endEvent1LabelMid.y
});
expect(getMid(endEvent2Label).x).to.be.closeTo(450, 1); // Label position was adjusted
expect(getMid(endEvent2Label).y).to.be.closeTo(272, 1); // Label position was adjusted
expect(sequenceFlow2.waypoints[ 0 ]).to.include({ x: sequenceFlow2Waypoints[ 0 ].x - 100, y: 240 });
expect(sequenceFlow2.waypoints[ 1 ]).to.include({ x: sequenceFlow2Waypoints[ 1 ].x - 100, y: 240 });
expect(sequenceFlow4.waypoints).to.have.length(4);
expect(subProcess1).to.have.bounds({
x: subProcess1Bounds.x,
y: subProcess1Bounds.y,
width: subProcess1Bounds.width - 100,
height: subProcess1Bounds.height
});
}));
it('should remove space bottom', inject(function() {
// given
var subProcess1 = $element('SubProcess_1');
var subProcess1Bounds = getBounds(subProcess1);
// when
makeSpace({ x: 420, y: 240 }, { dy: -100 });
// then
expect(subProcess1).to.have.bounds({
x: subProcess1Bounds.x,
y: subProcess1Bounds.y,
width: subProcess1Bounds.width,
height: 160
});
}));
it('should remove space left', inject(function() {
// given
var startEvent1 = $element('StartEvent_1'),
startEvent2 = $element('StartEvent_2'),
startEvent1Label = startEvent1.label,
startEvent2Label = startEvent2.label,
sequenceFlow1 = $element('SequenceFlow_1'),
sequenceFlow3 = $element('SequenceFlow_3'),
subProcess1 = $element('SubProcess_1');
var startEvent1Mid = getMid(startEvent1),
startEvent2Mid = getMid(startEvent2),
startEvent1LabelMid = getMid(startEvent1Label),
sequenceFlow1Waypoints = sequenceFlow1.waypoints.slice(),
subProcess1Bounds = getBounds(subProcess1);
// when
makeSpace({ x: 420, y: 240 }, { dx: 100 }, true);
// then
expect(getMid(startEvent1)).to.eql({
x: startEvent1Mid.x + 100,
y: startEvent1Mid.y
});
expect(getMid(startEvent2)).to.eql({
x: startEvent2Mid.x + 100,
y: startEvent2Mid.y
});
expect(getMid(startEvent1Label)).to.eql({
x: startEvent1LabelMid.x + 100,
y: startEvent1LabelMid.y
});
expect(getMid(startEvent2Label).x).to.be.closeTo(398, 1); // Label position was adjusted
expect(getMid(startEvent2Label).y).to.be.closeTo(272, 1); // Label position was adjusted
expect(sequenceFlow1.waypoints[ 0 ]).to.include({ x: sequenceFlow1Waypoints[ 0 ].x + 100, y: 240 });
expect(sequenceFlow1.waypoints[ 1 ]).to.include({ x: sequenceFlow1Waypoints[ 1 ].x + 100, y: 240 });
expect(sequenceFlow3.waypoints).to.have.length(4);
expect(subProcess1).to.have.bounds({
x: subProcess1Bounds.x + 100,
y: subProcess1Bounds.y,
width: subProcess1Bounds.width - 100,
height: subProcess1Bounds.height
});
}));
});
});
describe('text annotations', function() {
var diagramXML = require('./BpmnSpaceTool.text-annotations.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
beforeEach(inject(function(dragging) {
dragging.setOptions({ manual: true });
}));
it('should not resize text annotations', inject(function(dragging, elementRegistry, spaceTool) {
// given
var textAnnotation = $element('TextAnnotation_1'),
textAnnotationMid = getMid(textAnnotation),
textAnnotationWidth = textAnnotation.width;
// when
spaceTool.activateMakeSpace(canvasEvent({ x: textAnnotationMid.x, y: 0 }));
dragging.move(canvasEvent({ x: textAnnotationMid.x + 100, y: 0 }));
dragging.end();
// then
expect(textAnnotation.width).to.equal(textAnnotationWidth);
}));
});
describe('boundary events', function() {
var diagramXML = require('./BpmnSpaceTool.boundary-events.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
beforeEach(inject(function(dragging) {
dragging.setOptions({ manual: true });
}));
it('should move boundary event when moving subprocess', inject(function() {
// given
var boundaryEvent = $element('BoundaryEvent_2');
var boundaryEventMid = getMid(boundaryEvent);
// when
makeSpace({ x: boundaryEventMid.x + 5, y: boundaryEventMid.y }, { dx: -100 }, true);
// then
expect(getMid(boundaryEvent)).to.eql({
x: boundaryEventMid.x - 100,
y: boundaryEventMid.y,
});
}));
it('should move boundary event when resizing subprocess', inject(function() {
// given
var boundaryEvent = $element('BoundaryEvent_2');
var boundaryEventMid = getMid(boundaryEvent);
// when
makeSpace({ x: boundaryEventMid.x - 5, y: boundaryEventMid.y }, { dx: 100 });
// then
expect(getMid(boundaryEvent)).to.eql({
x: boundaryEventMid.x + 100,
y: boundaryEventMid.y,
});
}));
it('should not move boundary event if subprocess not moving or resizing', inject(function() {
// given
var boundaryEvent = $element('BoundaryEvent_2');
var boundaryEventMid = getMid(boundaryEvent);
// when
makeSpace({ x: boundaryEventMid.x + 5, y: boundaryEventMid.y }, { dx: 100 });
// then
expect(getMid(boundaryEvent)).to.eql(boundaryEventMid);
}));
});
describe('participants', function() {
var diagramXML = require('./BpmnSpaceTool.participants.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
beforeEach(inject(function(dragging) {
dragging.setOptions({ manual: true });
}));
it('should resize an expanded pool horizontally', inject(function() {
// given
var participant1 = $element('Participant_1');
var participant1Bounds = getBounds(participant1);
// when
makeSpace({ x: 200, y: 90 }, { dx: -100 }, true);
// then
expect(participant1).to.have.bounds({
x: participant1Bounds.x - 100,
y: participant1Bounds.y,
width: participant1Bounds.width + 100,
height: participant1Bounds.height
});
}));
it('should resize an expanded pool vertically', inject(function() {
// given
var participant1 = $element('Participant_1');
var participant1Bounds = getBounds(participant1);
// when
makeSpace({ x: 200, y: 90 }, { dy: -100 }, true);
// then
expect(participant1).to.have.bounds({
x: participant1Bounds.x,
y: participant1Bounds.y - 100,
width: participant1Bounds.width,
height: participant1Bounds.height + 100
});
}));
it('should resize an empty pool horizontally', inject(function() {
// given
var participant2 = $element('Participant_2');
var participant2Bounds = getBounds(participant2);
// when
makeSpace({ x: 200, y: 180 }, { dx: -100 }, true);
// then
expect(participant2).to.have.bounds({
x: participant2Bounds.x - 100,
y: participant2Bounds.y,
width: participant2Bounds.width + 100,
height: participant2Bounds.height
});
}));
it('should not resize an empty pool vertically', inject(function() {
// given
var participant2 = $element('Participant_2');
var participant2Bounds = getBounds(participant2);
// when
makeSpace({ x: 200, y: 180 }, { dy: -100 }, true);
// then
expect(participant2).to.have.bounds({
x: participant2Bounds.x,
y: participant2Bounds.y,
width: participant2Bounds.width,
height: participant2Bounds.height
});
}));
});
describe('artifacts', function() {
var diagramXML = require('./BpmnSpaceTool.artifacts.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
beforeEach(inject(function(dragging) {
dragging.setOptions({ manual: true });
}));
describe('should move visually contained', function() {
it('in participant', inject(function() {
// given
var textAnnotation = $element('ANNOTATION_1');
var textAnnotation_X = textAnnotation.x;
var group = $element('GROUP_CONTAINED_PARTICIPANT');
var group_X = group.x;
// when
makeSpace(leftOf(group), { dx: 100 }, false, 'PARTICIPANT');
// then
expect(textAnnotation.x).to.equal(textAnnotation_X + 100);
expect(group.x).to.equal(group_X + 100);
}));
it('in subprocess', inject(function() {
// given
var textAnnotation = $element('ANNOTATION_4');
var textAnnotation_X = textAnnotation.x;
var group = $element('GROUP_CONTAINED_SUB');
var group_X = group.x;
// when
makeSpace(leftOf(group), { dx: 100 }, false, 'SUB');
// then
expect(textAnnotation.x).to.equal(textAnnotation_X + 100);
expect(group.x).to.equal(group_X + 100);
}));
it('on root', inject(function() {
// given
var textAnnotation_in_PARTICIPANT = $element('ANNOTATION_1');
var textAnnotation_in_PARTICIPANT_X = textAnnotation_in_PARTICIPANT.x;
var textAnnotation_in_SUB = $element('ANNOTATION_4');
var textAnnotation_in_SUB_X = textAnnotation_in_SUB.x;
var group_in_PARTICIPANT = $element('GROUP_CONTAINED_PARTICIPANT');
var group_in_PARTICIPANT_X = group_in_PARTICIPANT.x;
var group_in_SUB = $element('GROUP_CONTAINED_SUB');
var group_in_SUB_X = group_in_SUB.x;
// when
makeSpace(leftOf('PARTICIPANT'), { dx: 100 }, false);
// then
expect(textAnnotation_in_PARTICIPANT.x).to.equal(textAnnotation_in_PARTICIPANT_X + 100);
expect(textAnnotation_in_SUB.x).to.equal(textAnnotation_in_SUB_X + 100);
expect(group_in_PARTICIPANT.x).to.equal(group_in_PARTICIPANT_X + 100);
expect(group_in_SUB.x).to.equal(group_in_SUB_X + 100);
}));
});
describe('should ignore outside of containment', function() {
it('in participant', inject(function() {
// given
var textAnnotation = $element('ANNOTATION_3');
var textAnnotation_X = textAnnotation.x;
var group = $element('GROUP_OUTSIDE');
var group_X = group.x;
// when
makeSpace(leftOf('TASK'), { dx: 100 }, false, 'PARTICIPANT');
// then
expect(textAnnotation.x).to.equal(textAnnotation_X);
expect(group.x).to.equal(group_X);
}));
it('in subprocess', inject(function() {
// given
var textAnnotation = $element('ANNOTATION_5');
var textAnnotation_X = textAnnotation.x;
var group = $element('GROUP_OUTSIDE_SUB');
var group_X = group.x;
// when
makeSpace(leftOf('GROUP_CONTAINED_SUB'), { dx: 100 }, false, 'SUB');
// then
expect(textAnnotation.x).to.equal(textAnnotation_X);
expect(group.x).to.equal(group_X);
}));
});
describe('should ignore unaffected inside of containment', function() {
it('in participant', inject(function() {
// given
var textAnnotation = $element('ANNOTATION_1');
var textAnnotation_X = textAnnotation.x;
var group = $element('GROUP_CONTAINED_PARTICIPANT');
var group_X = group.x;
// when
makeSpace(leftOf(group), { dx: -100 }, true, 'PARTICIPANT');
// then
expect(textAnnotation.x).to.equal(textAnnotation_X);
expect(group.x).to.equal(group_X);
}));
it('in subprocess', inject(function() {
// given
var textAnnotation = $element('ANNOTATION_4');
var textAnnotation_X = textAnnotation.x;
var group = $element('GROUP_CONTAINED_SUB');
var group_X = group.x;
// when
makeSpace(leftOf(group), { dx: -100 }, true, 'SUB');
// then
expect(textAnnotation.x).to.equal(textAnnotation_X);
expect(group.x).to.equal(group_X);
}));
});
});
});
// helpers //////////
function makeSpace(start, delta, invert, target) {
var modifier = invert ? invertModifier : {};
var end = {
x: start.x + (delta.dx || 0),
y: start.y + (delta.dy || 0)
};
return getBpmnJS().invoke(function(spaceTool, dragging, canvas) {
spaceTool.activateMakeSpace(canvasEvent(start));
if (target) {
target = $element(target);
dragging.hover({
element: target,
gfx: canvas.getGraphics(target)
});
}
dragging.move(canvasEvent(end, modifier));
dragging.end();
});
}
function $element(id) {
if (!isString(id)) {
return id;
}
return getBpmnJS().invoke(function(elementRegistry) {
const element = elementRegistry.get(id);
expect(element, `element <#${id}>`).to.exist;
return element;
});
}
function leftOf(element) {
element = $element(element);
const mid = getMid(element);
return {
x: element.x - 10,
y: mid.y
};
}
// eslint-disable-next-line "no-unused-vars"
function rightOf(element) {
element = $element(element);
const mid = getMid(element);
return {
x: element.x + element.width + 10,
y: mid.y
};
}
function getBounds(shape) {
return pick(shape, [ 'x', 'y', 'width', 'height' ]);
}
================================================
FILE: test/spec/helper/InjectSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import coreModule from 'lib/core';
import { expect } from 'chai';
describe('helper - inject', function() {
var diagramXML = require('../../fixtures/bpmn/simple.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: [
coreModule
]
}));
it('should work with Promise as return value', function() {
// given
var expected = 'resolved';
// when
var testFn = inject(function(eventBus) {
expect(eventBus).to.exist;
return Promise.resolve(expected);
});
// then
return testFn().then(function(result) {
expect(result).to.eql(expected);
});
});
it('should handle Promise rejection', function() {
// given
var expected = new Error('rejected');
function onResolved() {
throw new Error('should not resolve');
}
function onRejected(error) {
expect(error).to.eql(expected);
}
// when
var testFn = inject(function(eventBus) {
expect(eventBus).to.exist;
return Promise.reject(expected);
});
// then
return testFn().then(onResolved, onRejected);
});
});
================================================
FILE: test/spec/i18n/custom-translate/custom-translate.js
================================================
import translate from 'diagram-js/lib/i18n/translate/translate';
export default function customTranslate(template, replacements) {
if (template === 'Delete') {
template = 'Entfernen';
}
if (template === 'Activate hand tool') {
template = 'Hand-Tool aktivieren';
}
return translate(template, replacements);
}
================================================
FILE: test/spec/i18n/custom-translate/index.js
================================================
import customTranslate from './custom-translate';
export default {
translate: [ 'value', customTranslate ]
};
================================================
FILE: test/spec/i18n/translateSpec.js
================================================
import {
bootstrapModeler,
collectTranslations,
inject
} from 'test/TestHelper';
import coreModule from 'lib/core';
import translateModule from 'diagram-js/lib/i18n/translate';
import customTranslateModule from './custom-translate';
import modelingModule from 'lib/features/modeling';
import paletteModule from 'lib/features/palette';
import contextPadModule from 'lib/features/context-pad';
var diagramXML = require('test/fixtures/bpmn/simple.bpmn');
// skipping this file during translation extraction
collectTranslations ? describe.skip : describe('i18n - translate', function() {
beforeEach(bootstrapModeler(diagramXML, {
modules: [
coreModule,
modelingModule,
paletteModule,
contextPadModule,
translateModule,
customTranslateModule
]
}));
it('should translate palette', inject(function(palette) {
// when
var handToolEntry = palette.getEntries()['hand-tool'];
// then
expect(handToolEntry.title).to.equal('Hand-Tool aktivieren');
}));
it('should translate context pad', inject(function(contextPad) {
// given
contextPad.open('Task_1');
// when
var deleteEntry = contextPad._current.entries.delete;
// then
expect(deleteEntry.title).to.equal('Entfernen');
}));
});
================================================
FILE: test/spec/import/BpmnTreeWalkerSpec.js
================================================
import BpmnTreeWalker from 'lib/import/BpmnTreeWalker';
import { BpmnModdle } from 'bpmn-moddle';
import { find } from 'min-dash';
import simpleXML from 'test/fixtures/bpmn/simple.bpmn';
import collaboration from 'test/fixtures/bpmn/collaboration.bpmn';
describe('import - BpmnTreeWalker', function() {
it('should expose functions', function() {
// when
var walker = createWalker();
// then
expect(walker.handleDeferred).to.exist;
expect(walker.handleDefinitions).to.exist;
expect(walker.handleSubProcess).to.exist;
expect(walker.registerDi).to.exist;
});
it('should walk bpmn:Definitions', function() {
// given
var elementSpy = sinon.spy(),
rootSpy = sinon.spy(),
errorSpy = sinon.spy();
var walker = createWalker({
element: elementSpy,
root: rootSpy,
error: errorSpy
});
return createModdle(simpleXML).then(function(result) {
var definitions = result.rootElement;
// when
walker.handleDefinitions(definitions);
// then
expect(elementSpy.callCount).to.equal(8);
expect(rootSpy).to.be.calledOnce;
expect(errorSpy).not.to.be.called;
});
});
it('should always call element visitor with parent', function() {
// given
var elementSpy = sinon.spy(),
errorSpy = sinon.spy();
var walker = createWalker({
element: elementSpy,
root: function() {
return 'root';
},
error: errorSpy
});
return createModdle(collaboration).then(function(result) {
var definitions = result.rootElement;
// when
walker.handleDefinitions(definitions);
// then
expect(elementSpy).to.not.be.calledWith(sinon.match.any, sinon.match.typeOf('undefined'));
expect(errorSpy).to.not.be.called;
});
});
it('should walk bpmn:SubProcess', function() {
// given
var elementSpy = sinon.spy(),
rootSpy = sinon.spy(),
errorSpy = sinon.spy();
var walker = createWalker({
element: elementSpy,
root: rootSpy,
error: errorSpy
});
return createModdle(simpleXML).then(function(result) {
var definitions = result.rootElement;
var subProcess = findElementWithId(definitions, 'SubProcess_1');
var plane = definitions.diagrams[0].plane,
planeElements = plane.planeElement;
// register DI
planeElements.forEach(walker.registerDi);
// when
walker.handleSubProcess(subProcess);
walker.handleDeferred();
// then
expect(elementSpy.callCount).to.equal(3);
expect(rootSpy).to.not.be.called;
expect(errorSpy).to.not.be.called;
});
});
it('should error', function() {
// given
var elementSpy = sinon.spy(),
rootSpy = sinon.spy(),
errorSpy = sinon.spy();
var walker = createWalker({
element: elementSpy,
root: rootSpy,
error: errorSpy
});
return createModdle(simpleXML).then(function(result) {
var definitions = result.rootElement;
var element = findElementWithId(definitions, 'SubProcess_1');
// will error
definitions.diagrams[0].plane.planeElement.push({ bpmnElement: element });
// when
walker.handleDefinitions(definitions);
// then
expect(elementSpy.callCount).to.equal(8);
expect(rootSpy.calledOnce).to.be.true;
expect(errorSpy.calledOnce).to.be.true;
});
});
});
// helpers //////////
function createModdle(xml) {
var moddle = new BpmnModdle();
return moddle.fromXML(xml, 'bpmn:Definitions');
}
function createWalker(listeners) {
listeners = listeners || {};
var visitor = {
element: function(element, parent) {
return listeners.element && listeners.element(element, parent);
},
root: function(root) {
return listeners.root && listeners.root(root);
},
error: function(message, context) {
return listeners.error && listeners.error(message, context);
}
};
return new BpmnTreeWalker(visitor, function() {});
}
function findElementWithId(definitions, id) {
function findElement(element) {
if (element.id === id) {
return element;
}
if (element.flowElements) {
return find(element.flowElements, function(flowElement) {
var foundElement = findElement(flowElement);
return foundElement && foundElement.id === id;
});
}
}
return definitions.rootElements.reduce(function(foundElement, rootElement) {
if (rootElement.id === id) {
return rootElement;
} else {
return findElement(rootElement) || foundElement;
}
}, null);
}
================================================
FILE: test/spec/import/ImporterSpec.js
================================================
import TestContainer from 'mocha-test-container-support';
import Diagram from 'diagram-js/lib/Diagram';
import { BpmnModdle } from 'bpmn-moddle';
import {
importBpmnDiagram
} from 'lib/import/Importer';
import CoreModule from 'lib/core';
import {
matches as domMatches
} from 'min-dom';
import {
getChildren as getChildrenGfx
} from 'diagram-js/lib/util/GraphicsUtil';
import {
find
} from 'min-dash';
import { is } from 'lib/util/ModelUtil';
describe('import - Importer', function() {
function createDiagram(container, modules) {
return new Diagram({
canvas: { container: container },
modules: modules
});
}
var diagram;
beforeEach(function() {
diagram = createDiagram(TestContainer.get(this), [ CoreModule ]);
});
function runImport(diagram, xml, diagramId) {
var moddle = new BpmnModdle();
return moddle.fromXML(xml).then(function(result) {
var definitions = result.rootElement;
var selectedDiagram = find(definitions.diagrams, function(element) {
return element.id === diagramId;
});
return importBpmnDiagram(diagram, definitions, selectedDiagram);
}).then(function(result) {
return result;
});
}
describe('events', function() {
it('should fire and ', function() {
// given
var xml = require('../../fixtures/bpmn/import/process.bpmn');
var eventCount = 0;
var eventBus = diagram.get('eventBus');
// log events
eventBus.on('import.render.start', function(event) {
expect(event.definitions).to.exist;
eventCount++;
});
eventBus.on('import.render.complete', function(event) {
expect(event).to.have.property('error');
expect(event).to.have.property('warnings');
eventCount++;
});
// when
return runImport(diagram, xml).then(function() {
// then
expect(eventCount).to.equal(2);
});
});
it('should fire during import', function() {
// given
var xml = require('../../fixtures/bpmn/import/process.bpmn');
var eventCount = 0;
// log events
diagram.get('eventBus').on('bpmnElement.added', function(e) {
eventCount++;
});
// when
return runImport(diagram, xml).then(function() {
// then
expect(eventCount).to.equal(9);
});
});
});
describe('basics', function() {
it('should import process', function() {
// given
var xml = require('../../fixtures/bpmn/import/process.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.di.id,
diagramElement: e.element && e.element.id
});
});
// when
return runImport(diagram, xml).then(function() {
// then
expect(events).to.eql([
{ type: 'add', semantic: 'Process_1', di: 'BPMNPlane_1', diagramElement: 'Process_1' },
{ type: 'add', semantic: 'SubProcess_1', di: '_BPMNShape_SubProcess_2', diagramElement: 'SubProcess_1' },
{ type: 'add', semantic: 'StartEvent_1', di: '_BPMNShape_StartEvent_2', diagramElement: 'StartEvent_1' },
{ type: 'add', semantic: 'Task_1', di: '_BPMNShape_Task_2', diagramElement: 'Task_1' },
{ type: 'add', semantic: 'EndEvent_1', di: '_BPMNShape_EndEvent_2', diagramElement: 'EndEvent_1' },
{ type: 'add', semantic: 'StartEvent_2', di: '_BPMNShape_StartEvent_11', diagramElement: 'StartEvent_2' },
{ type: 'add', semantic: 'SequenceFlow_1', di: 'BPMNEdge_SequenceFlow_1', diagramElement: 'SequenceFlow_1' },
{ type: 'add', semantic: 'SequenceFlow_2', di: 'BPMNEdge_SequenceFlow_2', diagramElement: 'SequenceFlow_2' },
{ type: 'add', semantic: 'SequenceFlow_3', di: 'BPMNEdge_SequenceFlow_3', diagramElement: 'SequenceFlow_3' }
]);
expect(
diagram.get('canvas').getRootElement()
).to.equal(
diagram.get('elementRegistry').get('Process_1')
);
});
});
it('should import collaboration', function() {
// given
var xml = require('../../fixtures/bpmn/import/collaboration.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.di.id,
diagramElement: e.element && e.element.id
});
});
// when
return runImport(diagram, xml).then(function() {
// then
expect(events).to.eql([
{ type: 'add', semantic: '_Collaboration_2', di: 'BPMNPlane_1', diagramElement: '_Collaboration_2' },
{ type: 'add', semantic: 'Participant_2', di: '_BPMNShape_Participant_2', diagramElement: 'Participant_2' },
{ type: 'add', semantic: 'Task_1', di: '_BPMNShape_Task_3', diagramElement: 'Task_1' },
{ type: 'add', semantic: 'Participant_1', di: '_BPMNShape_Participant_3', diagramElement: 'Participant_1' },
{ type: 'add', semantic: 'StartEvent_1', di: '_BPMNShape_StartEvent_3', diagramElement: 'StartEvent_1' },
{ type: 'add', semantic: 'Lane_1', di: '_BPMNShape_Lane_2', diagramElement: 'Lane_1' },
{ type: 'add', semantic: 'Lane_2', di: '_BPMNShape_Lane_3', diagramElement: 'Lane_2' },
{ type: 'add', semantic: 'Lane_3', di: '_BPMNShape_Lane_4', diagramElement: 'Lane_3' }
]);
expect(
diagram.get('canvas').getRootElement()
).to.equal(
diagram.get('elementRegistry').get('_Collaboration_2')
);
});
});
});
describe('position', function() {
var xml = require('../../fixtures/bpmn/import/position/position-testcase.bpmn');
it('should round shape coordinates', function() {
// given
var events = {};
// log events
diagram.get('eventBus').on('bpmnElement.added', function(e) {
events[e.element.id] = e.element;
});
return runImport(diagram, xml).then(function() {
// round up
expect(events.ID_End.x).to.equal(Math.round(340.6));
expect(events.ID_End.y).to.equal(Math.round(136.6));
// round down
expect(events.ID_Start.x).to.equal(Math.round(120.4));
expect(events.ID_Start.y).to.equal(Math.round(135.4));
});
});
it('should round shape dimensions', function() {
// given
var events = {};
// log events
diagram.get('eventBus').on('bpmnElement.added', function(e) {
events[e.element.id] = e.element;
});
return runImport(diagram, xml).then(function() {
// round down
expect(events.ID_Start.height).to.equal(Math.round(30.4));
expect(events.ID_Start.width).to.equal(Math.round(30.4));
});
});
});
describe('order', function() {
it('should import lanes behind other flow nodes', function() {
var xml = require('./sequenceFlow-ordering.bpmn');
// given
var elementRegistry = diagram.get('elementRegistry');
return runImport(diagram, xml).then(function() {
// when
var processShape = elementRegistry.get('Participant_1jxpy8o');
var children = processShape.children;
// lanes
// other elements
var correctlyOrdered = [].concat(
children.filter(function(e) { return is(e, 'bpmn:Lane'); }),
children.filter(function(e) { return !is(e, 'bpmn:Lane'); })
);
// then
expectChildren(diagram, processShape, correctlyOrdered);
});
});
it('should import sequence flows in front of other flow nodes', function() {
var xml = require('./sequenceFlow-ordering.bpmn');
// given
var elementRegistry = diagram.get('elementRegistry');
return runImport(diagram, xml).then(function() {
// when
var processShape = elementRegistry.get('Participant_1jxpy8o');
var children = processShape.children;
// lanes
// other elements
// connections
// labels
var correctlyOrdered = [].concat(
children.filter(function(e) { return !e.waypoints && !e.labelTarget; }),
children.filter(function(e) { return e.waypoints; }),
children.filter(function(e) { return e.labelTarget; })
);
// then
expectChildren(diagram, processShape, correctlyOrdered);
});
});
it('should import DataAssociations in root', function() {
// given
var xml = require('./data-association.bpmn');
// given
var elementRegistry = diagram.get('elementRegistry');
// when
return runImport(diagram, xml).then(function() {
// then
var process = elementRegistry.get('Collaboration'),
association = elementRegistry.get('DataAssociation'),
dataStore = elementRegistry.get('DataStore');
expect(association.parent).to.eql(process);
expect(dataStore.parent).to.eql(process);
});
});
});
describe('elements', function() {
it('should import boundary events', function() {
// 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.di.id,
diagramElement: e.element && e.element.id
});
});
// when
return runImport(diagram, xml).then(function() {
// 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' }
]);
});
});
it('should import data store as child of participant', function() {
// given
var xml = require('../../fixtures/bpmn/import/data-store.inside-participant.bpmn');
var events = {};
// log events
diagram.get('eventBus').on('bpmnElement.added', function(e) {
events[e.element.id] = e.element;
});
return runImport(diagram, xml).then(function() {
expect(events.DataStoreReference.parent).to.equal(events.Participant);
});
});
it('should import data store in participant as child of collaboration', function() {
// given
var xml = require('../../fixtures/bpmn/import/data-store.outside-participant.participant.bpmn');
var events = {};
// log events
diagram.get('eventBus').on('bpmnElement.added', function(e) {
events[e.element.id] = e.element;
});
return runImport(diagram, xml).then(function() {
expect(events.DataStoreReference.parent).to.equal(events.Collaboration);
});
});
it('should import data store in subprocess as child of collaboration', function() {
// given
var xml = require('../../fixtures/bpmn/import/data-store.outside-participant.subprocess.bpmn');
var events = {};
// log events
diagram.get('eventBus').on('bpmnElement.added', function(e) {
events[e.element.id] = e.element;
});
return runImport(diagram, xml).then(function() {
expect(events.DataStoreReference.parent).to.equal(events.Collaboration);
});
});
it('should import data store outside of participant without warnings', function() {
// given
var xml = require('../../fixtures/bpmn/import/data-store.outside-participant.dangling.bpmn');
// when
return runImport(diagram, xml).then(function(result) {
var warnings = result.warnings;
// then
expect(warnings).to.be.empty;
});
});
it('should import single diagram from multiple diagrams 2', function() {
// given
var xml = require('../../fixtures/bpmn/import/multiple-diagrams.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.di.id,
diagramElement: e.element && e.element.id
});
});
// when
return runImport(diagram, xml, 'BPMNDiagram_2').then(function() {
// then
expect(events).to.eql([
{ type: 'add', semantic: 'Process_2', di: 'BPMNPlane_2', diagramElement: 'Process_2' },
{ type: 'add', semantic: 'StartEvent_2', di: '_BPMNShape_StartEvent_2', diagramElement: 'StartEvent_2' },
{ type: 'add', semantic: 'IntermediateThrowEvent_1', di: '_BPMNShape_IntermediateThrowEvent_1', diagramElement: 'IntermediateThrowEvent_1' },
{ type: 'add', semantic: 'EndEvent_2', di: '_BPMNShape_EndEvent_2', diagramElement: 'EndEvent_2' },
{ type: 'add', semantic: 'SequenceFlow_4', di: 'BPMNEdge_SequenceFlow_4', diagramElement: 'SequenceFlow_4' },
{ type: 'add', semantic: 'SequenceFlow_5', di: 'BPMNEdge_SequenceFlow_5', diagramElement: 'SequenceFlow_5' }
]);
expect(
diagram.get('canvas').getRootElement()
).to.equal(
diagram.get('elementRegistry').get('Process_2')
);
});
});
it('should import single diagram from multiple diagrams 1', function() {
// given
var xml = require('../../fixtures/bpmn/import/multiple-diagrams.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.di.id,
diagramElement: e.element && e.element.id
});
});
// when
return runImport(diagram, xml, 'BPMNDiagram_1').then(function() {
// then
expect(events).to.eql([
{ type: 'add', semantic: 'Process_1', di: 'BPMNPlane_1', diagramElement: 'Process_1' },
{ type: 'add', semantic: 'StartEvent_1', di: '_BPMNShape_StartEvent_1', diagramElement: 'StartEvent_1' },
{ type: 'add', semantic: 'Task_1', di: '_BPMNShape_Task_1', diagramElement: 'Task_1' },
{ type: 'add', semantic: 'Task_2', di: '_BPMNShape_Task_2', diagramElement: 'Task_2' },
{ type: 'add', semantic: 'EndEvent_1', di: '_BPMNShape_EndEvent_1', diagramElement: 'EndEvent_1' },
{ type: 'add', semantic: 'SequenceFlow_1', di: 'BPMNEdge_SequenceFlow_1', diagramElement: 'SequenceFlow_1' },
{ type: 'add', semantic: 'SequenceFlow_2', di: 'BPMNEdge_SequenceFlow_2', diagramElement: 'SequenceFlow_2' },
{ type: 'add', semantic: 'SequenceFlow_3', di: 'BPMNEdge_SequenceFlow_3', diagramElement: 'SequenceFlow_3' }
]);
});
});
it('should import groups', function() {
// given
var xml = require('../../fixtures/bpmn/import/groups.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.di.id,
diagramElement: e.element && e.element.id,
isFrame: e.element && e.element.isFrame
});
});
// when
return runImport(diagram, xml).then(function() {
// then
expect(events).to.eql([
{ type: 'add', semantic: 'Process_1', di: 'BPMNPlane_1', diagramElement: 'Process_1', isFrame: undefined },
{ type: 'add', semantic: 'Group_1', di: 'Group_1_di', diagramElement: 'Group_1', isFrame: true }
]);
});
});
});
describe('forgiveness', function() {
it('should import invalid flowElement', function() {
// given
var xml = require('../../fixtures/bpmn/import/error/invalid-flow-element.bpmn');
// when
return runImport(diagram, xml).then(function(result) {
var warnings = result.warnings;
// then
expect(warnings).to.have.length(0);
});
});
it('should import multiple DIs', function() {
// given
var xml = require('../../fixtures/bpmn/import/error/multiple-dis.bpmn');
// when
return runImport(diagram, xml).then(function(result) {
var warnings = result.warnings;
var expectedMessage =
'multiple DI elements defined for ';
expect(warnings).to.have.length(1);
expect(warnings[0].message).to.equal(expectedMessage);
});
});
it('should import with missing BPMNDiagram#plane DI', function() {
// given
var xml = require('./missing-di-plane.bpmn');
// when
return runImport(diagram, xml).then(function(result) {
var {
error,
warnings
} = result;
// then
expect(warnings).to.be.empty;
expect(error).not.to.exist;
});
});
it('should error import with missing BPMNDiagram#plane DI', function() {
// given
var xml = require('./missing-di-plane-root-element.bpmn');
// when
return runImport(diagram, xml).then(function(result) {
var {
error,
warnings
} = result;
// then
// warning: no bpmnElement referenced in
// warning: correcting missing bpmnElement on
expect(warnings).to.have.length(2);
expect(error).not.to.exist;
});
});
it('should import sequence flow without waypoints', function() {
// given
var xml = require('./sequenceFlow-missingWaypoints.bpmn');
// when
return runImport(diagram, xml).then(function(result) {
var warnings = result.warnings;
// then
expect(warnings).to.be.empty;
});
});
it('should extend attributes with default value', function() {
// given
var xml = require('../../fixtures/bpmn/import/default-attrs.bpmn');
// when
return runImport(diagram, xml).then(function() {
var elementRegistry = diagram.get('elementRegistry');
var element = elementRegistry.get('GATEWAY_1');
expect(element.businessObject.eventGatewayType).to.equal('Exclusive');
});
});
describe('boundary events', function() {
it('should handle missing attachToRef', function() {
// given
var xml = require('../../fixtures/bpmn/import/error/boundaryEvent-missingAttachToRef.bpmn');
// when
return runImport(diagram, xml).then(function(result) {
var warnings = result.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');
});
});
it('should handle invalid attachToRef', function() {
// given
var xml = require('../../fixtures/bpmn/import/error/boundaryEvent-invalidAttachToRef.bpmn');
// when
return runImport(diagram, xml).then(function(result) {
var warnings = result.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');
});
});
});
});
describe('integration', function() {
it('should import dangling process message flows', function() {
// given
var xml = require('../../fixtures/bpmn/import/error/dangling-process-message-flow.bpmn');
// when
return runImport(diagram, xml).then(function(result) {
var warnings = result.warnings;
// then
expect(warnings).to.have.length(0);
expect(diagram.get('elementRegistry').get('_b467921a-ef7b-44c5-bf78-fd624c400d17')).to.exist;
expect(diagram.get('elementRegistry').get('_c311cc87-677e-47a4-bdb1-8744c4ec3147')).to.exist;
});
});
it('should import text annotations of message flows', function() {
// given
var xml = require('../../fixtures/bpmn/import/text-annotation-message-flow.bpmn');
// when
return runImport(diagram, xml).then(function() {
// then
var textAnnotation = diagram.get('elementRegistry').get('TextAnnotation_1');
var messageFlow = diagram.get('elementRegistry').get('MessageFlow_1');
var association = diagram.get('elementRegistry').get('Association_1');
expect(textAnnotation).to.exist;
expect(association.source).to.equal(messageFlow);
expect(association.target).to.equal(textAnnotation);
});
});
});
describe('hiding', function() {
it('should hide shapes and connections inside of collapsed subprocess', function() {
// given
var xml = require('../../fixtures/bpmn/import/collapsed/processWithChildren.bpmn');
// when
return runImport(diagram, xml).then(function() {
var elementRegistry = diagram.get('elementRegistry');
var children = elementRegistry.get('SubProcess_1').children;
var visible = find(children, function(child) {
return !child.hidden;
});
// then
expect(visible).to.be.undefined;
});
});
});
describe('multiple bpmndi:BPMNDiagram elements', function() {
it('should import first bpmndi:BPMNDiagram (default)', function() {
// given
var xml = require('../../fixtures/bpmn/multiple-diagrams.bpmn');
// when
return runImport(diagram, xml).then(function(result) {
var warnings = result.warnings;
// then
expect(warnings).to.have.length(0);
diagram.invoke(function(elementRegistry, canvas) {
expect(elementRegistry.get('Task_A')).to.exist;
expect(elementRegistry.get('Task_B')).not.to.exist;
expect(canvas.getRootElement()).to.equal(elementRegistry.get('Process_1'));
});
});
});
it('should import second bpmndi:BPMNDiagram (specified)', function() {
// given
var xml = require('../../fixtures/bpmn/multiple-diagrams.bpmn');
var selectedDiagram = 'BpmnDiagram_2';
// when
return runImport(diagram, xml, selectedDiagram).then(function(result) {
var warnings = result.warnings;
// then
expect(warnings).to.have.length(0);
diagram.invoke(function(elementRegistry, canvas) {
expect(elementRegistry.get('Task_A')).not.to.exist;
expect(elementRegistry.get('Task_B')).to.exist;
expect(canvas.getRootElement()).to.equal(elementRegistry.get('Process_2'));
});
});
});
it('should add root element for each bpmndi:BPMNDiagram when importing', function() {
// given
var xml = require('../../fixtures/bpmn/multiple-diagrams.bpmn');
// when
return runImport(diagram, xml).then(function() {
var elementRegistry = diagram.get('elementRegistry'),
canvas = diagram.get('canvas'),
rootA = elementRegistry.get('Process_1'),
rootB = elementRegistry.get('Process_2'),
taskA = elementRegistry.get('Task_A'),
taskB = elementRegistry.get('Task_B');
var activeRoot = canvas.getRootElement();
// then
expect(canvas.findRoot(taskA)).to.equal(rootA);
expect(canvas.findRoot(taskB)).to.equal(rootB);
expect(activeRoot).to.equal(rootA);
});
});
describe('collapsed sub process', function() {
it('should import collapsed sub process', function() {
// given
var xml = require('../../fixtures/bpmn/multiple-nested-processes.bpmn');
var selectedDiagram = 'BpmnDiagram_1';
// when
return runImport(diagram, xml, selectedDiagram).then(function(result) {
var warnings = result.warnings;
// then
expect(warnings).to.have.length(0);
diagram.invoke(function(elementRegistry, canvas) {
expect(elementRegistry.get('SubProcess_1')).to.exist;
expect(elementRegistry.get('Task_1A')).to.exist;
expect(elementRegistry.get('Task_1B')).to.exist;
expect(elementRegistry.get('SubProcess_2')).to.not.exist;
expect(canvas.getRootElement()).to.equal(elementRegistry.get('Process_1'));
});
});
});
it('should import and show collapsed sub process', function() {
// given
var xml = require('../../fixtures/bpmn/multiple-nested-processes.bpmn');
var selectedDiagram = 'SubProcessDiagram_1';
// when
return runImport(diagram, xml, selectedDiagram).then(function(result) {
var warnings = result.warnings;
// then
expect(warnings).to.have.length(0);
diagram.invoke(function(elementRegistry, canvas) {
expect(elementRegistry.get('SubProcess_1')).to.exist;
expect(elementRegistry.get('Task_1A')).to.exist;
expect(elementRegistry.get('Task_1B')).to.exist;
expect(elementRegistry.get('SubProcess_2')).to.not.exist;
expect(canvas.getRootElement()).to.equal(elementRegistry.get('SubProcess_1_plane'));
});
});
});
it('should import first bpmndi:BPMNDiagram when importing collapsed sub process', function() {
// given
var xml = require('../../fixtures/bpmn/multiple-nested-processes.bpmn');
var selectedDiagram = 'BpmnDiagram_2';
// when
return runImport(diagram, xml, selectedDiagram).then(function(result) {
var warnings = result.warnings;
// then
expect(warnings).to.have.length(0);
diagram.invoke(function(elementRegistry, canvas) {
expect(elementRegistry.get('SubProcess_2')).to.exist;
expect(elementRegistry.get('Task_2A')).to.exist;
expect(elementRegistry.get('Task_2B')).to.not.exist;
expect(elementRegistry.get('SubProcess_1')).to.not.exist;
expect(canvas.getRootElement()).to.equal(elementRegistry.get('Process_2'));
});
});
});
it('should import specified bpmndi:BPMNDiagram when importing collapsed sub process', function() {
// given
var xml = require('../../fixtures/bpmn/multiple-nested-processes.bpmn');
var selectedDiagram = 'SubProcess_2_diagram_B';
// when
return runImport(diagram, xml, selectedDiagram).then(function(result) {
var warnings = result.warnings;
// then
expect(warnings).to.have.length(0);
diagram.invoke(function(elementRegistry, canvas) {
expect(elementRegistry.get('SubProcess_2')).to.exist;
expect(elementRegistry.get('Task_2A')).to.not.exist;
expect(elementRegistry.get('Task_2B')).to.exist;
expect(elementRegistry.get('SubProcess_1')).to.not.exist;
expect(canvas.getRootElement()).to.equal(elementRegistry.get('SubProcess_2_plane'));
});
});
});
it('should add root element when importing collapsed sub process', function() {
// given
var xml = require('../../fixtures/bpmn/import/collapsed-subprocess.bpmn');
// when
return runImport(diagram, xml).then(function(result) {
var warnings = result.warnings;
// then
expect(warnings).to.have.length(0);
diagram.invoke(function(elementRegistry, canvas) {
var subProcessRoot = elementRegistry.get('Subprocess_plane');
var processRoot = elementRegistry.get('Process_1rjrv55');
var subProcessElement = elementRegistry.get('Subprocess');
var taskInSubProcessElement = elementRegistry.get('Task_B');
expect(subProcessRoot).to.exist;
expect(subProcessElement).to.exist;
expect(taskInSubProcessElement).to.exist;
expect(canvas.getRootElement()).to.equal(processRoot);
expect(subProcessElement.parent).to.equal(processRoot);
expect(taskInSubProcessElement.parent).to.equal(subProcessRoot);
});
});
});
});
});
});
// helpers //////////////////////
function expectChildren(diagram, parent, children) {
return diagram.invoke(function(elementRegistry) {
// verify model is consistent
expect(parent.children).to.eql(children);
// verify SVG is consistent
var parentGfx = elementRegistry.getGraphics(parent);
var expectedChildrenGfx = children.map(function(c) {
return elementRegistry.getGraphics(c);
});
var childrenContainerGfx =
domMatches(parentGfx, '[data-element-id="Process_1"]')
? parentGfx
: getChildrenGfx(parentGfx);
var existingChildrenGfx = Array.prototype.map.call(childrenContainerGfx.childNodes, function(c) {
return c.querySelector('.djs-element');
});
expect(existingChildrenGfx).to.eql(expectedChildrenGfx);
});
}
================================================
FILE: test/spec/import/ModelWiringSpec.js
================================================
import {
bootstrapViewer,
inject
} from 'test/TestHelper';
import {
is,
getDi
} from 'lib/util/ModelUtil';
describe('import - model wiring', function() {
describe('basics', function() {
var xml = require('../../fixtures/bpmn/import/process.bpmn');
beforeEach(bootstrapViewer(xml));
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(4);
}));
it('should wire label relationship', inject(function(elementRegistry) {
// when
var startEventShape = elementRegistry.get('StartEvent_1');
var label = startEventShape.label;
// then
expect(label).to.exist;
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.exist;
expect(is(subProcess, 'bpmn:SubProcess')).to.be.true;
expect(startEvent).to.exist;
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 = getDi(subProcessShape);
// then
expect(subProcessDi).to.exist;
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 = getDi(sequenceFlowElement);
// then
expect(sequenceFlowDi).to.exist;
expect(sequenceFlowDi.bpmnElement).to.eql(sequenceFlow);
}));
it('should wire label di', inject(function(elementRegistry) {
// when
var eventShape = elementRegistry.get('StartEvent_2');
var eventLabel = elementRegistry.get('StartEvent_2_label');
// assume
expect(eventShape).to.exist;
expect(eventLabel).to.exist;
// label relationship wired
expect(eventShape.label).to.eql(eventLabel);
expect(eventLabel.labelTarget).to.eql(eventShape);
// moddle relationships wired
expect(eventShape.di).to.exist;
expect(eventShape.businessObject).to.exist;
expect(eventShape.di).to.eql(eventLabel.di);
expect(eventShape.businessObject).to.eql(eventLabel.businessObject);
}));
});
describe('host attacher relationship', function() {
var xml = require('../../fixtures/bpmn/import/boundaryEvent.bpmn');
beforeEach(bootstrapViewer(xml));
it('should wire boundary event', inject(function(elementRegistry) {
// when
var boundaryEventShape = elementRegistry.get('BoundaryEvent_1'),
boundaryEvent = boundaryEventShape.businessObject;
var taskShape = elementRegistry.get('Task_1'),
task = taskShape.businessObject;
// assume
expect(boundaryEvent.attachedToRef).to.eql(task);
// then
expect(boundaryEventShape.host).to.eql(taskShape);
expect(taskShape.attachers).to.exist;
expect(taskShape.attachers).to.contain(boundaryEventShape);
}));
});
describe('lanes + flow elements', function() {
var xml = require('./lane-flowNodes.bpmn');
beforeEach(bootstrapViewer(xml));
it('should import flowElements as children of Participant', inject(function(elementRegistry) {
// when
var participantShape = elementRegistry.get('Participant_Lane'),
taskShape = elementRegistry.get('Task'),
sequenceFlowElement = elementRegistry.get('SequenceFlow');
// then
expect(taskShape.parent).to.eql(participantShape);
expect(sequenceFlowElement.parent).to.eql(participantShape);
}));
it('should wire FlowElement#lanes', inject(function(elementRegistry) {
// when
var taskShape = elementRegistry.get('Task'),
task = taskShape.businessObject,
laneShape = elementRegistry.get('Lane'),
lane = laneShape.businessObject;
// then
expect(task.get('lanes')).to.eql([ lane ]);
}));
});
describe('lanes + flow elements / missing flowNodeRef', function() {
var xml = require('./lane-missing-flowNodeRef.bpmn');
beforeEach(bootstrapViewer(xml));
it('should import flowElements as children of Participant', inject(function(elementRegistry) {
// when
var participantShape = elementRegistry.get('Participant_Lane'),
taskShape = elementRegistry.get('Task');
// then
// task is part of participant, as no lane was assigned
expect(taskShape.parent).to.eql(participantShape);
}));
});
});
================================================
FILE: test/spec/import/data-association.bpmn
================================================
DataStore
Property_1eojfva
================================================
FILE: test/spec/import/elements/AssociationSpec.collaboration.bpmn
================================================
DataObjectReference_1
DataObjectReference_1
Property_02nwoq1
================================================
FILE: test/spec/import/elements/AssociationSpec.compensation.bpmn
================================================
================================================
FILE: test/spec/import/elements/AssociationSpec.data-association.bpmn
================================================
DataObjectReference_1
DataObjectReference_1
DataObjectReference_1
================================================
FILE: test/spec/import/elements/AssociationSpec.data-input-output.bpmn
================================================
DataInput_1
DataOutput_1
================================================
FILE: test/spec/import/elements/AssociationSpec.events.bpmn
================================================
DataObjectReference
DataObjectReference
Property_1vxbjyx
================================================
FILE: test/spec/import/elements/AssociationSpec.js
================================================
import {
bootstrapViewer,
inject
} from 'test/TestHelper';
describe('import - associations', function() {
describe('should import association', function() {
it('connecting task -> text annotation', function() {
var xml = require('./AssociationSpec.text-annotation.bpmn');
// given
return bootstrapViewer(xml)().then(function(result) {
var err = result.error;
expect(err).not.to.exist;
// when
inject(function(elementRegistry) {
var association = elementRegistry.get('Association_1');
// then
expect(association).to.exist;
})();
});
});
it('connecting boundary -> compensate task', function() {
var xml = require('./AssociationSpec.compensation.bpmn');
// given
return bootstrapViewer(xml)().then(function(result) {
var err = result.error;
expect(err).not.to.exist;
// when
inject(function(elementRegistry) {
var association = elementRegistry.get('Association_1');
// then
expect(association).to.exist;
})();
});
});
});
describe('should import data association', function() {
function expectRendered(elementIds) {
inject(function(elementRegistry, canvas) {
elementIds.forEach(function(id) {
var element = elementRegistry.get(id);
// then
expect(element).to.exist;
// data associations always rendered on root
expect(element.parent).to.eql(canvas.getRootElement());
});
})();
}
it('task -> data object -> task', function() {
var xml = require('./AssociationSpec.data-association.bpmn');
// given
return bootstrapViewer(xml)().then(function(result) {
var err = result.error;
// then
expect(err).not.to.exist;
expectRendered([
'DataInputAssociation',
'DataOutputAssociation'
]);
});
});
it('data input -> task -> data output', function() {
var xml = require('./AssociationSpec.data-input-output.bpmn');
// given
return bootstrapViewer(xml)().then(function(result) {
var err = result.error;
// then
expect(err).not.to.exist;
expectRendered([
'DataInputAssociation',
'DataOutputAssociation'
]);
});
});
it('in collaboration', function() {
var xml = require('./AssociationSpec.collaboration.bpmn');
// given
return bootstrapViewer(xml)().then(function(result) {
var err = result.error;
// then
expect(err).not.to.exist;
expectRendered([
'DataInputAssociation',
'DataOutputAssociation'
]);
});
});
it('catch event -> data object -> throw event', function() {
var xml = require('./AssociationSpec.events.bpmn');
// given
return bootstrapViewer(xml)().then(function(result) {
var err = result.error;
// then
expect(err).not.to.exist;
expectRendered([
'DataInputAssociation',
'DataOutputAssociation'
]);
});
});
it('boundary event -> data object', function() {
var xml = require('./AssociationSpec.data-association.bpmn');
// given
return bootstrapViewer(xml)().then(function(result) {
var err = result.error;
// then
expect(err).not.to.exist;
expectRendered([
'DataOutputAssociation_2'
]);
});
});
});
});
================================================
FILE: test/spec/import/elements/AssociationSpec.text-annotation.bpmn
================================================
annotation
================================================
FILE: test/spec/import/elements/CollapsedSpec.js
================================================
import {
bootstrapViewer,
inject
} from 'test/TestHelper';
describe('import - collapsed container', function() {
describe('in process', function() {
var diagramXML = require('../../../fixtures/bpmn/import/collapsed/process.bpmn');
beforeEach(bootstrapViewer(diagramXML));
it('should import collapsed subProcess', inject(function(elementRegistry, canvas) {
var collapsedShape = elementRegistry.get('SubProcess_1');
var subProcessRoot = canvas.findRoot(collapsedShape);
var childShape = elementRegistry.get('IntermediateCatchEvent_1');
var childRoot = canvas.findRoot(childShape);
expect(collapsedShape.collapsed).to.be.true;
expect(subProcessRoot).not.to.equal(childRoot);
}));
it('should import collapsed transaction', inject(function(elementRegistry, canvas) {
var collapsedShape = elementRegistry.get('Transaction_1');
var subProcessRoot = canvas.findRoot(collapsedShape);
var childShape = elementRegistry.get('UserTask_1');
var childRoot = canvas.findRoot(childShape);
expect(collapsedShape.collapsed).to.be.true;
expect(subProcessRoot).not.to.eql(childRoot);
}));
it('should import collapsed adhocSubProcess', inject(function(elementRegistry, canvas) {
var collapsedShape = elementRegistry.get('AdHocSubProcess_1');
var subProcessRoot = canvas.findRoot(collapsedShape);
var childShape = elementRegistry.get('StartEvent_1');
var childRoot = canvas.findRoot(childShape);
expect(collapsedShape.collapsed).to.be.true;
expect(subProcessRoot).not.to.eql(childRoot);
}));
it('should import collapsed with nested elements', inject(function(elementRegistry, canvas) {
var collapsedShape = elementRegistry.get('SubProcess_4');
var subProcessRoot = canvas.findRoot(collapsedShape);
var childShape = elementRegistry.get('SubProcess_5');
var childRoot = canvas.findRoot(childShape);
var nestedChildShape = elementRegistry.get('Task_3');
var nestedChildRoot = canvas.findRoot(nestedChildShape);
expect(collapsedShape.collapsed).to.be.true;
expect(childRoot).not.to.eql(subProcessRoot);
expect(nestedChildRoot).not.to.eql(subProcessRoot);
}));
it('should import collapsed with nested hidden labels', inject(function(elementRegistry, canvas) {
var collapsedShape = elementRegistry.get('SubProcess_2');
var subProcessRoot = canvas.findRoot(collapsedShape);
var hiddenEventShape = elementRegistry.get('StartEvent_2');
var hiddenEventRoot = canvas.findRoot(hiddenEventShape);
expect(hiddenEventRoot).not.to.eql(subProcessRoot);
var hiddenDataShape = elementRegistry.get('DataObjectReference_1');
var hiddenDataRoot = canvas.findRoot(hiddenDataShape);
expect(hiddenDataRoot).not.to.eql(subProcessRoot);
}));
it('should import expanded subProcess', inject(function(elementRegistry, canvas) {
var expandedShape = elementRegistry.get('SubProcess_3');
var expandedRoot = canvas.findRoot(expandedShape);
var childShape = elementRegistry.get('Task_2');
var childRoot = canvas.findRoot(childShape);
expect(expandedShape.collapsed).to.be.false;
expect(childShape.hidden).to.be.false;
expect(expandedRoot).to.eql(childRoot);
}));
});
describe('in collaboration', function() {
var diagramXML = require('../../../fixtures/bpmn/import/collapsed/collaboration.bpmn');
beforeEach(bootstrapViewer(diagramXML));
it('should import collapsed subProcess in pool', inject(function(elementRegistry, canvas) {
var collapsedShape = elementRegistry.get('SubProcess_1');
var subProcessRoot = canvas.findRoot(collapsedShape);
var childShape = elementRegistry.get('Task_1');
var childRoot = canvas.findRoot(childShape);
expect(collapsedShape.collapsed).to.be.true;
expect(subProcessRoot).not.to.eql(childRoot);
}));
it('should import expanded subProcess in pool', inject(function(elementRegistry, canvas) {
var expandedShape = elementRegistry.get('SubProcess_2');
var expandedRoot = canvas.findRoot(expandedShape);
var childShape = elementRegistry.get('StartEvent_1');
var childRoot = canvas.findRoot(childShape);
expect(expandedShape.collapsed).to.be.false;
expect(childShape.hidden).to.be.false;
expect(expandedRoot).to.eql(childRoot);
}));
it('should import collapsed subProcess in lane', inject(function(elementRegistry, canvas) {
var expandedShape = elementRegistry.get('SubProcess_4');
var subProcessRoot = canvas.findRoot(expandedShape);
var childShape = elementRegistry.get('Task_2');
var childRoot = canvas.findRoot(childShape);
expect(expandedShape.collapsed).to.be.true;
expect(subProcessRoot).not.to.eql(childRoot);
}));
it('should import expanded subProcess in lane', inject(function(elementRegistry, canvas) {
var expandedShape = elementRegistry.get('SubProcess_3');
var expandedRoot = canvas.findRoot(expandedShape);
var childShape = elementRegistry.get('StartEvent_2');
var childRoot = canvas.findRoot(childShape);
expect(expandedShape.collapsed).to.be.false;
expect(childShape.hidden).to.be.false;
expect(expandedRoot).to.eql(childRoot);
}));
});
});
================================================
FILE: test/spec/import/elements/DataInputOutput.bpmn
================================================
DataInput
DataOutput
Task_DataInput
Task_DataOutput
DataInput
Task_DataInput
Task_DataOutput
DataOutput
================================================
FILE: test/spec/import/elements/DataInputOutputSpec.js
================================================
import {
bootstrapViewer,
inject
} from 'test/TestHelper';
describe('import - data input/output', function() {
describe('should import external labels', function() {
it('with di', function() {
var xml = require('./DataInputOutput.bpmn');
// given
return bootstrapViewer(xml).call(this).then(function(result) {
var err = result.error;
expect(err).not.to.exist;
// when
inject(function(elementRegistry) {
var inputLabel = elementRegistry.get('DataInput').label,
outputLabel = elementRegistry.get('DataOutput').label;
var inputLabelCenter = getCenter(inputLabel),
outputCenter = getCenter(outputLabel);
// then
expect(inputLabelCenter.x).to.be.within(110, 130);
expect(inputLabelCenter.y).to.be.within(150, 170);
expect(inputLabel.width).to.be.above(20);
expect(inputLabel.height).to.be.above(10);
expect(outputCenter.x).to.be.within(290, 310);
expect(outputCenter.y).to.be.within(190, 210);
expect(outputLabel.width).to.be.above(20);
expect(outputLabel.height).to.be.above(10);
})();
});
});
});
});
// helper ////////////////
function getCenter(element) {
return {
x: element.x + Math.ceil(element.width / 2),
y: element.y + Math.ceil(element.height / 2)
};
}
================================================
FILE: test/spec/import/elements/Groups.bpmn
================================================
================================================
FILE: test/spec/import/elements/GroupsSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
describe('import - groups', function() {
describe('should import groups', function() {
it('with frame property set', function() {
var xml = require('./Groups.bpmn');
// given
return bootstrapModeler(xml)().then(function(result) {
var err = result.error;
expect(err).not.to.exist;
// when
inject(function(elementRegistry) {
// then
var groupElement = elementRegistry.get('Group_1');
expect(groupElement).to.exist;
expect(groupElement.isFrame).to.be.true;
})();
});
});
});
});
================================================
FILE: test/spec/import/elements/LabelSpec.js
================================================
import {
bootstrapViewer,
inject
} from 'test/TestHelper';
describe('import - labels', function() {
describe('should import embedded labels', function() {
it('on flow nodes', function() {
var xml = require('../../../fixtures/bpmn/import/labels/embedded.bpmn');
return bootstrapViewer(xml)().then(function(result) {
expect(result.error).not.to.exist;
});
});
it('on pools and lanes', function() {
var xml = require('../../../fixtures/bpmn/import/labels/collaboration.bpmn');
return bootstrapViewer(xml)().then(function(result) {
expect(result.error).not.to.exist;
});
});
it('on message flows', function() {
var xml = require('../../../fixtures/bpmn/import/labels/collaboration-message-flows.bpmn');
return bootstrapViewer(xml)().then(function(result) {
expect(result.error).not.to.exist;
});
});
});
describe('should import external labels', function() {
it('with di', function() {
var xml = require('../../../fixtures/bpmn/import/labels/external.bpmn');
// given
return bootstrapViewer(xml)().then(function(result) {
var err = result.error;
expect(err).not.to.exist;
// when
inject(function(elementRegistry) {
var eventLabel = elementRegistry.get('EndEvent_1').label,
sequenceFlowLabel = elementRegistry.get('SequenceFlow_1').label;
var eventLabelCenter = getCenter(eventLabel),
sequenceFlowCenter = getCenter(sequenceFlowLabel);
// then
expect(eventLabelCenter.x).to.be.within(270, 272);
expect(eventLabelCenter.y).to.be.within(269, 271);
expect(eventLabel.width).to.be.above(65);
expect(eventLabel.height).to.be.above(20);
expect(sequenceFlowCenter.x).to.be.within(481, 483);
expect(sequenceFlowCenter.y).to.be.within(323, 335);
expect(sequenceFlowLabel.width).to.be.above(64);
expect(sequenceFlowLabel.height).to.be.above(11);
})();
});
});
it('without di', function() {
var xml = require('../../../fixtures/bpmn/import/labels/external-no-di.bpmn');
return bootstrapViewer(xml)().then(function(result) {
expect(result.error).not.to.exist;
});
});
});
});
// helper ////////////////
function getCenter(element) {
return {
x: element.x + Math.ceil(element.width / 2),
y: element.y + Math.ceil(element.height / 2)
};
}
================================================
FILE: test/spec/import/lane-flowNodes.bpmn
================================================
Other_Task
Task
SequenceFlow
SequenceFlow
================================================
FILE: test/spec/import/lane-missing-flowNodeRef.bpmn
================================================
================================================
FILE: test/spec/import/missing-di-plane-root-element.bpmn
================================================
================================================
FILE: test/spec/import/missing-di-plane.bpmn
================================================
================================================
FILE: test/spec/import/sequenceFlow-missingWaypoints.bpmn
================================================
SequenceFlow
SequenceFlow
================================================
FILE: test/spec/import/sequenceFlow-ordering.bpmn
================================================
Task_16z77et
StartEvent_1
Task_14uuigv
EndEvent_0exo24i
Task_1y8k07l
ExclusiveGateway_0xyt6ot
SequenceFlow_1q8max9
SequenceFlow_1q8max9
SequenceFlow_01mqkl9
SequenceFlow_01mqkl9
================================================
FILE: test/spec/util/ModelUtilSpec.js
================================================
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import coreModule from 'lib/core';
import modelingModule from 'lib/features/modeling';
import {
is,
isAny,
getDi
} from 'lib/util/ModelUtil';
describe('util/ModelUtil', function() {
var diagramXML = require('../../fixtures/bpmn/simple.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: [
coreModule,
modelingModule
]
}));
describe('#is', function() {
it('should work with diagram element', inject(function(elementFactory) {
// given
var messageFlowConnection = elementFactory.createConnection({ type: 'bpmn:MessageFlow' });
// then
expect(is(messageFlowConnection, 'bpmn:MessageFlow')).to.be.true;
expect(is(messageFlowConnection, 'bpmn:BaseElement')).to.be.true;
expect(is(messageFlowConnection, 'bpmn:SequenceFlow')).to.be.false;
expect(is(messageFlowConnection, 'bpmn:Task')).to.be.false;
}));
it('should work with business object', inject(function(bpmnFactory) {
// given
var gateway = bpmnFactory.create('bpmn:Gateway');
// then
expect(is(gateway, 'bpmn:Gateway')).to.be.true;
expect(is(gateway, 'bpmn:BaseElement')).to.be.true;
expect(is(gateway, 'bpmn:SequenceFlow')).to.be.false;
}));
it('should work with untyped business object', inject(function() {
// given
var foo = { businessObject: 'BAR' };
// then
expect(is(foo, 'FOO')).to.be.false;
}));
it('should work with untyped diagram element', inject(function() {
// given
var foo = { };
// then
expect(is(foo, 'FOO')).to.be.false;
}));
});
describe('isAny', function() {
it('should work on shape', inject(function(bpmnFactory, elementFactory) {
// given
var element = elementFactory.createShape({ type: 'bpmn:Gateway' });
// then
expect(isAny(element, [ 'bpmn:Gateway' ])).to.be.true;
expect(isAny(element, [ 'bpmn:SequenceFlow', 'bpmn:Gateway' ])).to.be.true;
expect(isAny(element, [ 'bpmn:BaseElement' ])).to.be.true;
expect(isAny(element, [ 'bpmn:SequenceFlow' ])).to.be.false;
}));
it('should work on businessObject', inject(function(bpmnFactory, elementFactory) {
// given
var businessObject = bpmnFactory.create('bpmn:Gateway');
// then
expect(isAny(businessObject, [ 'bpmn:Gateway' ])).to.be.true;
expect(isAny(businessObject, [ 'bpmn:SequenceFlow', 'bpmn:Gateway' ])).to.be.true;
expect(isAny(businessObject, [ 'bpmn:BaseElement' ])).to.be.true;
expect(isAny(businessObject, [ 'bpmn:SequenceFlow' ])).to.be.false;
}));
});
describe('#getDi', function() {
it('return a di', inject(function() {
// given
var element = { di: 'foo' };
// then
expect(getDi(element)).to.equal('foo');
}));
it('should ignore element without di', inject(function() {
// given
var element = { };
// then
expect(getDi(element)).to.be.undefined;
}));
});
});
================================================
FILE: test/spec/util/svgHelpersSpec.js
================================================
import { expectSvgPath, compareSvgPaths, pathToNumbers } from '../../util/svgHelpers';
describe('test helpers - svgHelpers', function() {
describe('expectSvgPath', function() {
it('should return true for equal paths', function() {
// given
const pathA = 'M10,20L30,40';
const pathB = 'M10,20L30,40';
// then
expectSvgPath(pathA, pathB);
});
it('should return true for approximately equal paths', function() {
// given
const pathA = 'M187,263m0,-18a18,18,0,1,1,0,36a18,18,0,1,1,0,-36z';
const pathB = 'M186,262m0,-18a18,18,0,1,1,0,36a18,18,0,1,1,0,-34z';
// then
expectSvgPath(pathA, pathB, 3);
});
});
describe('compareSvgPaths', function() {
it('should return false for different paths', function() {
// given
const pathA = 'M187,263m0,-18a13,13,0,1,1,0,36a18,18,0,1,1,0,-36z';
const pathB = 'M181,262m0,-18a13,13,0,1,1,0,36a18,18,0,1,1,0,-34z';
// when
const equal = compareSvgPaths(pathA, pathB, 3);
// then
expect(equal).to.be.false;
});
it('should return false for paths of different length', function() {
// given
const pathA = 'M10,20L30,40';
const pathB = 'M10,20L30,40L50,60';
// when
const equal = compareSvgPaths(pathA, pathB, 3);
// then
expect(equal).to.be.false;
});
});
describe('pathToNumbers', function() {
it('should extract numbers from path', function() {
// when
const numbers = pathToNumbers('M10,20L30,40');
// then
expect(numbers).to.eql([ 10, 20, 30, 40 ]);
});
});
});
================================================
FILE: test/testBundle.js
================================================
var allTests = require.context('.', true, /(spec|integration).*Spec\.js$/);
allTests.keys().forEach(allTests);
================================================
FILE: test/util/KeyEvents.js
================================================
import {
isString,
assign
} from 'min-dash';
/**
* Create a fake key event for testing purposes.
*
* @param {string|number} key the key or keyCode/charCode
* @param {Object} [attrs]
*
* @return {Event}
*/
export function createKeyEvent(key, attrs) {
var event = document.createEvent('Events') || new document.defaultView.CustomEvent('keyEvent');
// init and mark as bubbles / cancelable
event.initEvent('keydown', false, true);
var keyAttrs = isString(key) ? { key: key } : { keyCode: key, which: key };
return assign(event, keyAttrs, attrs || {});
}
================================================
FILE: test/util/MockEvents.js
================================================
import {
assign
} from 'min-dash';
import {
getBpmnJS
} from 'test/TestHelper';
/**
* Create an event with global coordinates
* computed based on the loaded diagrams canvas position and the
* specified canvas local coordinates.
*
* @param {Point} point of the event local the canvas (closure)
* @param {Object} [data]
*
* @return {Event} event, scoped to the given canvas
*/
export function createCanvasEvent(position, data) {
return getBpmnJS().invoke(function(canvas) {
var target = canvas._svg;
var clientRect = canvas._container.getBoundingClientRect();
var absolutePosition = {
x: position.x + clientRect.left,
y: position.y + clientRect.top
};
return createEvent(target, absolutePosition, data);
});
}
/**
* Create an Event
*
* @param {Element} target
* @param { { x: number, y: number } } position
* @param {any} [data]
*
* @return {Event}
*/
export function createEvent(target, position, data) {
return getBpmnJS().invoke(function(eventBus) {
data = assign({
target: target,
x: position.x,
y: position.y,
clientX: position.x,
clientY: position.y,
offsetX: position.x,
offsetY: position.y,
button: 0
}, data || {});
return eventBus.createEvent(data);
});
}
================================================
FILE: test/util/custom-rules/CustomRules.js
================================================
import inherits from 'inherits-browser';
import RuleProvider from 'diagram-js/lib/features/rules/RuleProvider';
export default function CustomRules(eventBus) {
RuleProvider.call(this, eventBus);
}
CustomRules.$inject = [ 'eventBus' ];
inherits(CustomRules, RuleProvider);
CustomRules.prototype.init = function() {
// placeholder
};
================================================
FILE: test/util/custom-rules/index.js
================================================
import CustomRules from './CustomRules';
export default {
__init__: [ 'customRules' ],
customRules: [ 'type', CustomRules ]
};
================================================
FILE: test/util/svgHelpers.js
================================================
/**
* Assert if two SVG paths are approximately equal within a given tolerance.
*
* @param {string} actual
* @param {string} expected
* @param {number} [tolerance=2]
*/
export function expectSvgPath(actual, expected, tolerance = 2) {
const result = compareSvgPaths(actual, expected, tolerance);
expect(result).to.equal(true, `expected SVG path "${actual}" to approximately equal "${expected}"`);
}
/**
* Returns true if two SVG paths are approximately equal within a given tolerance.
*
* @param {string} pathA
* @param {string} pathB
* @param {number} [tolerance=2]
* @returns {boolean}
*/
export function compareSvgPaths(pathA, pathB, tolerance = 2) {
const actualNumbers = pathToNumbers(pathA);
const expectedNumbers = pathToNumbers(pathB);
if (actualNumbers.length !== expectedNumbers.length) {
return false;
}
for (let i = 0; i < actualNumbers.length; i++) {
if (Math.abs(actualNumbers[i] - expectedNumbers[i]) > tolerance) {
return false;
}
}
return true;
}
/**
* Get an array of numeric values from an SVG path string.
*
* @example
* `pathToNumber('M10,20L30,40')` => `[10, 20, 30, 40]`
*
* @param {string} path
* @returns {number[]}
*/
export function pathToNumbers(path) {
const normalized = path.toLowerCase();
const parts = normalized.split(/[a-z,]/g).filter(s => s !== '');
return parts.map(parseFloat);
}
================================================
FILE: tsconfig.json
================================================
{
"compilerOptions": {
"lib": [
"DOM",
"ES2018"
],
"strict": true
},
"include": [
"./lib/**/*.d.ts",
"./lib/**/*.spec.ts"
]
}