From 622e6ef579924f271730dfb500b62540f63d8e3d Mon Sep 17 00:00:00 2001 From: Maciej Barelkowski Date: Sat, 5 Feb 2022 15:51:45 +0100 Subject: [PATCH] feat: add bpmn:InclusiveGateway support Closes #88 --- lib/base.js | 2 + lib/features/context-pads/ContextPads.js | 2 + .../handler/InclusiveGatewayHandler.js | 47 +++++ .../element-support/ElementSupport.js | 1 - .../InclusiveGatewaySettings.js | 161 ++++++++++++++++ .../inclusive-gateway-settings/index.js | 11 ++ .../behaviors/InclusiveGatewayBehavior.js | 182 ++++++++++++++++++ lib/simulator/behaviors/index.js | 3 + test/spec/ModelerSpec.js | 2 +- test/spec/ViewerSpec.js | 2 +- ...ator.inclusive-gateway-boundary-event.bpmn | 134 +++++++++++++ ...ator.inclusive-gateway-boundary-event.json | 61 ++++++ ...ulator.inclusive-gateway-default-flow.bpmn | 102 ++++++++++ ...ulator.inclusive-gateway-default-flow.json | 34 ++++ ...tor.inclusive-gateway-multiple-starts.bpmn | 115 +++++++++++ ...tor.inclusive-gateway-multiple-starts.json | 66 +++++++ ...ive-gateway-single-token-pass-through.bpmn | 60 ++++++ ...ive-gateway-single-token-pass-through.json | 26 +++ .../Simulator.inclusive-gateway-sync.bpmn | 102 ++++++++++ .../Simulator.inclusive-gateway-sync.json | 41 ++++ test/spec/simulator/SimulatorSpec.js | 97 ++++++++++ 21 files changed, 1248 insertions(+), 3 deletions(-) create mode 100644 lib/features/context-pads/handler/InclusiveGatewayHandler.js create mode 100644 lib/features/inclusive-gateway-settings/InclusiveGatewaySettings.js create mode 100644 lib/features/inclusive-gateway-settings/index.js create mode 100644 lib/simulator/behaviors/InclusiveGatewayBehavior.js create mode 100644 test/spec/simulator/Simulator.inclusive-gateway-boundary-event.bpmn create mode 100644 test/spec/simulator/Simulator.inclusive-gateway-boundary-event.json create mode 100644 test/spec/simulator/Simulator.inclusive-gateway-default-flow.bpmn create mode 100644 test/spec/simulator/Simulator.inclusive-gateway-default-flow.json create mode 100644 test/spec/simulator/Simulator.inclusive-gateway-multiple-starts.bpmn create mode 100644 test/spec/simulator/Simulator.inclusive-gateway-multiple-starts.json create mode 100644 test/spec/simulator/Simulator.inclusive-gateway-single-token-pass-through.bpmn create mode 100644 test/spec/simulator/Simulator.inclusive-gateway-single-token-pass-through.json create mode 100644 test/spec/simulator/Simulator.inclusive-gateway-sync.bpmn create mode 100644 test/spec/simulator/Simulator.inclusive-gateway-sync.json diff --git a/lib/base.js b/lib/base.js index 58a4091b..035a31fe 100644 --- a/lib/base.js +++ b/lib/base.js @@ -13,6 +13,7 @@ import SetAnimationSpeedModule from './features/set-animation-speed'; import ExclusiveGatewaySettingsModule from './features/exclusive-gateway-settings'; import NeutralElementColors from './features/neutral-element-colors'; +import InclusiveGatewaySettingsModule from './features/inclusive-gateway-settings'; import TokenSimulationPaletteModule from './features/palette'; export default { @@ -31,6 +32,7 @@ export default { SetAnimationSpeedModule, ExclusiveGatewaySettingsModule, NeutralElementColors, + InclusiveGatewaySettingsModule, TokenSimulationPaletteModule ] }; \ No newline at end of file diff --git a/lib/features/context-pads/ContextPads.js b/lib/features/context-pads/ContextPads.js index 0f133755..145b62d6 100644 --- a/lib/features/context-pads/ContextPads.js +++ b/lib/features/context-pads/ContextPads.js @@ -17,6 +17,7 @@ import { } from 'min-dom'; import ExclusiveGatewayHandler from './handler/ExclusiveGatewayHandler'; +import InclusiveGatewayHandler from './handler/InclusiveGatewayHandler'; import PauseHandler from './handler/PauseHandler'; import TriggerHandler from './handler/TriggerHandler'; @@ -45,6 +46,7 @@ export default function ContextPads( this._handlers = []; this.registerHandler('bpmn:ExclusiveGateway', ExclusiveGatewayHandler); + this.registerHandler('bpmn:InclusiveGateway', InclusiveGatewayHandler); this.registerHandler('bpmn:Activity', PauseHandler); diff --git a/lib/features/context-pads/handler/InclusiveGatewayHandler.js b/lib/features/context-pads/handler/InclusiveGatewayHandler.js new file mode 100644 index 00000000..d9e142ef --- /dev/null +++ b/lib/features/context-pads/handler/InclusiveGatewayHandler.js @@ -0,0 +1,47 @@ +import { + ForkIcon +} from '../../../icons'; + +import { getBusinessObject } from '../../../util/ElementHelper'; +import { isSequenceFlow } from '../../../simulator/util/ModelUtil'; + +export default function InclusiveGatewayHandler(inclusiveGatewaySettings) { + this._inclusiveGatewaySettings = inclusiveGatewaySettings; +} + +InclusiveGatewayHandler.prototype.createContextPads = function(element) { + const outgoingFlows = element.outgoing.filter(isSequenceFlow); + + if (outgoingFlows.length < 2) { + return; + } + + const nonDefaultFlows = outgoingFlows.filter(outgoing => { + const flowBo = getBusinessObject(outgoing), + gatewayBo = getBusinessObject(element); + + return gatewayBo.default !== flowBo; + }); + + const html = ` +
+ ${ForkIcon()} +
+ `; + + return nonDefaultFlows.map(sequenceFlow => { + const action = () => { + this._inclusiveGatewaySettings.toggleSequenceFlow(element, sequenceFlow); + }; + + return { + action, + element: sequenceFlow, + html + }; + }); +}; + +InclusiveGatewayHandler.$inject = [ + 'inclusiveGatewaySettings' +]; \ No newline at end of file diff --git a/lib/features/element-support/ElementSupport.js b/lib/features/element-support/ElementSupport.js index a7443273..915cd47b 100644 --- a/lib/features/element-support/ElementSupport.js +++ b/lib/features/element-support/ElementSupport.js @@ -16,7 +16,6 @@ import { const UNSUPPORTED_ELEMENTS = [ - 'bpmn:InclusiveGateway', 'bpmn:ComplexGateway' ]; diff --git a/lib/features/inclusive-gateway-settings/InclusiveGatewaySettings.js b/lib/features/inclusive-gateway-settings/InclusiveGatewaySettings.js new file mode 100644 index 00000000..341bd84f --- /dev/null +++ b/lib/features/inclusive-gateway-settings/InclusiveGatewaySettings.js @@ -0,0 +1,161 @@ +import { + TOGGLE_MODE_EVENT +} from '../../util/EventHelper'; + + +const SELECTED_COLOR = '--token-simulation-grey-darken-30'; +const NOT_SELECTED_COLOR = '--token-simulation-grey-lighten-56'; + +import { + getBusinessObject, + is, + isSequenceFlow +} from '../../simulator/util/ModelUtil'; + +const COLOR_ID = 'inclusive-gateway-settings'; + + +export default function InclusiveGatewaySettings( + eventBus, elementRegistry, + elementColors, simulator, simulationStyles) { + + this._elementRegistry = elementRegistry; + this._elementColors = elementColors; + this._simulator = simulator; + this._simulationStyles = simulationStyles; + + eventBus.on(TOGGLE_MODE_EVENT, event => { + if (event.active) { + this.setDefaults(); + } else { + this.reset(); + } + }); +} + +InclusiveGatewaySettings.prototype.setDefaults = function() { + const inclusiveGateways = this._elementRegistry.filter(element => { + return is(element, 'bpmn:InclusiveGateway'); + }); + + inclusiveGateways.forEach(inclusiveGateway => { + if (inclusiveGateway.outgoing.filter(isSequenceFlow).length > 1) { + this._setGatewayDefaults(inclusiveGateway); + } + }); +}; + +InclusiveGatewaySettings.prototype.reset = function() { + const inclusiveGateways = this._elementRegistry.filter(element => { + return is(element, 'bpmn:InclusiveGateway'); + }); + + inclusiveGateways.forEach(inclusiveGateway => { + if (inclusiveGateway.outgoing.filter(isSequenceFlow).length > 1) { + this._resetGateway(inclusiveGateway); + } + }); +}; + +InclusiveGatewaySettings.prototype.toggleSequenceFlow = function(gateway, sequenceFlow) { + const activeOutgoing = this._getActiveOutgoing(gateway), + defaultFlow = getDefaultFlow(gateway), + nonDefaultFlows = getNonDefaultFlows(gateway); + + let newActiveOutgoing; + if (activeOutgoing.includes(sequenceFlow)) { + newActiveOutgoing = without(activeOutgoing, sequenceFlow); + } else { + newActiveOutgoing = without(activeOutgoing, defaultFlow).concat(sequenceFlow); + } + + // make sure at least one flow is active + if (!newActiveOutgoing.length) { + + // default flow if available + if (defaultFlow) { + newActiveOutgoing = [ defaultFlow ]; + } else { + + // or another flow which is not the one toggled + newActiveOutgoing = [ nonDefaultFlows.find(flow => flow !== sequenceFlow) ]; + } + } + + this._setActiveOutgoing(gateway, newActiveOutgoing); +}; + +InclusiveGatewaySettings.prototype._getActiveOutgoing = function(gateway) { + const { + activeOutgoing + } = this._simulator.getConfig(gateway); + + return activeOutgoing; +}; + +InclusiveGatewaySettings.prototype._setActiveOutgoing = function(gateway, activeOutgoing) { + this._simulator.setConfig(gateway, { activeOutgoing }); + + const sequenceFlows = gateway.outgoing.filter(isSequenceFlow); + + // set colors + sequenceFlows.forEach(outgoing => { + + const style = (!activeOutgoing || activeOutgoing.includes(outgoing)) ? + SELECTED_COLOR : NOT_SELECTED_COLOR; + const stroke = this._simulationStyles.get(style); + + this._elementColors.add(outgoing, COLOR_ID, { + stroke + }); + }); +}; + +InclusiveGatewaySettings.prototype._setGatewayDefaults = function(gateway) { + const sequenceFlows = gateway.outgoing.filter(isSequenceFlow); + + const defaultFlow = getDefaultFlow(gateway); + const nonDefaultFlows = without(sequenceFlows, defaultFlow); + + this._setActiveOutgoing(gateway, nonDefaultFlows); +}; + +InclusiveGatewaySettings.prototype._resetGateway = function(gateway) { + this._setActiveOutgoing(gateway, undefined); +}; + +InclusiveGatewaySettings.$inject = [ + 'eventBus', + 'elementRegistry', + 'elementColors', + 'simulator', + 'simulationStyles' +]; + +function getDefaultFlow(gateway) { + const defaultFlow = getBusinessObject(gateway).default; + + if (!defaultFlow) { + return; + } + + return gateway.outgoing.find(flow => { + const flowBo = getBusinessObject(flow); + + return flowBo === defaultFlow; + }); +} + +function getNonDefaultFlows(gateway) { + const defaultFlow = getDefaultFlow(gateway); + + return gateway.outgoing.filter(flow => { + const flowBo = getBusinessObject(flow); + + return flowBo !== defaultFlow; + }); +} + +function without(array, element) { + return array.filter(arrayElement => arrayElement !== element); +} diff --git a/lib/features/inclusive-gateway-settings/index.js b/lib/features/inclusive-gateway-settings/index.js new file mode 100644 index 00000000..b57133a5 --- /dev/null +++ b/lib/features/inclusive-gateway-settings/index.js @@ -0,0 +1,11 @@ +import InclusiveGatewaySettings from './InclusiveGatewaySettings'; +import ElementColorsModule from '../element-colors'; +import SimulationStylesModule from '../simulation-styles'; + +export default { + __depends__: [ + ElementColorsModule, + SimulationStylesModule + ], + inclusiveGatewaySettings: [ 'type', InclusiveGatewaySettings ] +}; \ No newline at end of file diff --git a/lib/simulator/behaviors/InclusiveGatewayBehavior.js b/lib/simulator/behaviors/InclusiveGatewayBehavior.js new file mode 100644 index 00000000..3acf271b --- /dev/null +++ b/lib/simulator/behaviors/InclusiveGatewayBehavior.js @@ -0,0 +1,182 @@ +import { + filterSequenceFlows, isSequenceFlow +} from '../util/ModelUtil'; + + +export default function InclusiveGatewayBehavior( + simulator, + activityBehavior) { + + this._simulator = simulator; + this._activityBehavior = activityBehavior; + + simulator.registerBehavior('bpmn:InclusiveGateway', this); +} + +InclusiveGatewayBehavior.prototype.enter = function(context) { + + // join right away if possible + if (this._canJoin(context)) { + this._join(context); + return; + } + + // join when all reachable tokens exit + // do not subscribe if a token of current parent scope is already waiting + const elementScopes = this._getElementScopes(context); + if (elementScopes.length > 1) { + return; + } + + this._joinWhenPossible(context); +}; + +InclusiveGatewayBehavior.prototype.exit = function(context) { + + const { + element, + scope + } = context; + + // depends on UI to properly configure activeOutgoing for + // each inclusive gateway + + const outgoings = filterSequenceFlows(element.outgoing); + + if (outgoings.length === 1) { + return this._simulator.enter({ + element: outgoings[0], + scope: scope.parent + }); + } + + const { + activeOutgoing = [] + } = this._simulator.getConfig(element); + + if (!activeOutgoing.length) { + throw new Error('no outgoing configured'); + } + + for (const outgoing of activeOutgoing) { + this._simulator.enter({ + element: outgoing, + scope: scope.parent + }); + } +}; + +/** + * Returns true if there are either no remaining scopes in the parent scope, or if they are not + * reachable from the current element. + * + * @returns {Boolean} + */ +InclusiveGatewayBehavior.prototype._canJoin = function(context) { + const remainingScopes = this._getRemainingScopes(context); + + // There are still some tokens to wait for. + if (remainingScopes.length && this._canReachAnyScope(remainingScopes, context.element)) { + return false; + } + + return true; +}; + +InclusiveGatewayBehavior.prototype._joinWhenPossible = function(context) { + const { + scope + } = context; + const remainingScopes = this._getRemainingScopes(context); + + const event = this._simulator.waitForScopes(scope, remainingScopes); + + const subscription = this._simulator.subscribe(scope, event, () => { + subscription.remove(); + + if (this._canJoin(context)) { + this._join(context); + } else { + + // resubscribe to wait for remaining scopes + this._joinWhenPossible(context); + } + }); +}; + +InclusiveGatewayBehavior.prototype._getRemainingScopes = function(context) { + const { + scope, + element + } = context; + + const { + parent: parentScope + } = scope; + + return this._simulator.findScopes(scope => ( + scope.parent === parentScope && scope.element !== element)); +}; + +InclusiveGatewayBehavior.prototype._join = function(context) { + const elementScopes = this._getElementScopes(context); + + for (const childScope of elementScopes) { + + if (childScope !== context.scope) { + + // complete joining child scope + this._simulator.destroyScope(childScope.complete(), context.scope); + } + } + + this._simulator.exit(context); +}; + +InclusiveGatewayBehavior.prototype._getElementScopes = function(context) { + const { + element, + scope + } = context; + + return this._simulator.findScopes({ + parent: scope.parent, + element + }); +}; + +InclusiveGatewayBehavior.prototype._canReachAnyScope = function(scopes, currentElement, traversed = new Set()) { + + // avoid infinite recursion + if (traversed.has(currentElement)) { + return false; + } + traversed.add(currentElement); + + if (anyScopeIsOnElement(scopes, currentElement)) { + return true; + } + + if (isSequenceFlow(currentElement)) { + return this._canReachAnyScope(scopes, currentElement.source, traversed); + } + + const incomingFlows = filterSequenceFlows(currentElement.incoming); + + for (const flow of incomingFlows) { + if (this._canReachAnyScope(scopes, flow, traversed)) { + return true; + } + } + + return false; +}; + +InclusiveGatewayBehavior.$inject = [ + 'simulator', + 'activityBehavior' +]; + +function anyScopeIsOnElement(scopes, element) { + return scopes.some(scope => scope.element === element); +} diff --git a/lib/simulator/behaviors/index.js b/lib/simulator/behaviors/index.js index c2a30cfc..9ffd87ac 100644 --- a/lib/simulator/behaviors/index.js +++ b/lib/simulator/behaviors/index.js @@ -7,6 +7,7 @@ import IntermediateThrowEventBehavior from './IntermediateThrowEventBehavior'; import ExclusiveGatewayBehavior from './ExclusiveGatewayBehavior'; import ParallelGatewayBehavior from './ParallelGatewayBehavior'; import EventBasedGatewayBehavior from './EventBasedGatewayBehavior'; +import InclusiveGatewayBehavior from './InclusiveGatewayBehavior'; import ActivityBehavior from './ActivityBehavior'; import SubProcessBehavior from './SubProcessBehavior'; @@ -31,6 +32,7 @@ export default { 'exclusiveGatewayBehavior', 'parallelGatewayBehavior', 'eventBasedGatewayBehavior', + 'inclusiveGatewayBehavior', 'subProcessBehavior', 'sequenceFlowBehavior', 'messageFlowBehavior', @@ -44,6 +46,7 @@ export default { exclusiveGatewayBehavior: [ 'type', ExclusiveGatewayBehavior ], parallelGatewayBehavior: [ 'type', ParallelGatewayBehavior ], eventBasedGatewayBehavior: [ 'type', EventBasedGatewayBehavior ], + inclusiveGatewayBehavior: [ 'type', InclusiveGatewayBehavior ], activityBehavior: [ 'type', ActivityBehavior ], subProcessBehavior: [ 'type', SubProcessBehavior ], sequenceFlowBehavior: [ 'type', SequenceFlowBehavior ], diff --git a/test/spec/ModelerSpec.js b/test/spec/ModelerSpec.js index fe3e447a..89d4396a 100644 --- a/test/spec/ModelerSpec.js +++ b/test/spec/ModelerSpec.js @@ -194,7 +194,7 @@ describe('modeler extension', function() { // then expect( elementSupport.getUnsupportedElements() - ).to.have.length(2); + ).to.have.length(1); })); }); diff --git a/test/spec/ViewerSpec.js b/test/spec/ViewerSpec.js index 80bba18c..96bd01ec 100644 --- a/test/spec/ViewerSpec.js +++ b/test/spec/ViewerSpec.js @@ -53,7 +53,7 @@ describe('viewer extension', function() { // then expect( elementSupport.getUnsupportedElements() - ).to.have.length(2); + ).to.have.length(1); })); }); diff --git a/test/spec/simulator/Simulator.inclusive-gateway-boundary-event.bpmn b/test/spec/simulator/Simulator.inclusive-gateway-boundary-event.bpmn new file mode 100644 index 00000000..417455ab --- /dev/null +++ b/test/spec/simulator/Simulator.inclusive-gateway-boundary-event.bpmn @@ -0,0 +1,134 @@ + + + + + Flow_2 + + + Flow_1 + + + + + + + + Flow_2 + Flow_3 + Flow_4 + Flow_5 + + + Flow_4 + Flow_5 + Flow_0j4wzlf + Flow_1 + + + Flow_3 + Flow_0j4wzlf + + + + Flow_0k3cxv5 + + + + Flow_0k3cxv5 + + + + FAKE + + + + FAKE + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/spec/simulator/Simulator.inclusive-gateway-boundary-event.json b/test/spec/simulator/Simulator.inclusive-gateway-boundary-event.json new file mode 100644 index 00000000..c4dafa33 --- /dev/null +++ b/test/spec/simulator/Simulator.inclusive-gateway-boundary-event.json @@ -0,0 +1,61 @@ +[ + "createScope:Process_1:null", + "signal:Process_1:B", + "createScope:START:B", + "signal:START:C", + "exit:START:C", + "createScope:Flow_2:B", + "destroyScope:START:C", + "enter:Flow_2:B", + "exit:Flow_2:D", + "createScope:F_GATE:B", + "destroyScope:Flow_2:D", + "enter:F_GATE:B", + "exit:F_GATE:E", + "createScope:Flow_3:B", + "createScope:Flow_4:B", + "createScope:Flow_5:B", + "destroyScope:F_GATE:E", + "enter:Flow_3:B", + "enter:Flow_4:B", + "enter:Flow_5:B", + "exit:Flow_3:F", + "createScope:PausedActivity:B", + "destroyScope:Flow_3:F", + "exit:Flow_4:G", + "createScope:J_GATE:B", + "destroyScope:Flow_4:G", + "exit:Flow_5:H", + "createScope:J_GATE:B", + "destroyScope:Flow_5:H", + "enter:PausedActivity:B", + "enter:J_GATE:B", + "enter:J_GATE:B", + "createScope:TimerEvent:B", + "signal:TimerEvent:L", + "exit:PausedActivity:I", + "destroyScope:PausedActivity:I", + "destroyScope:J_GATE:K", + "exit:TimerEvent:L", + "createScope:Flow_0k3cxv5:B", + "destroyScope:TimerEvent:L", + "exit:J_GATE:J", + "createScope:Flow_1:B", + "destroyScope:J_GATE:J", + "enter:Flow_0k3cxv5:B", + "enter:Flow_1:B", + "exit:Flow_0k3cxv5:M", + "createScope:Event_07226lb:B", + "destroyScope:Flow_0k3cxv5:M", + "exit:Flow_1:N", + "createScope:END:B", + "destroyScope:Flow_1:N", + "enter:Event_07226lb:B", + "enter:END:B", + "exit:Event_07226lb:O", + "destroyScope:Event_07226lb:O", + "exit:END:P", + "destroyScope:END:P", + "exit:Process_1:B", + "destroyScope:Process_1:B" +] \ No newline at end of file diff --git a/test/spec/simulator/Simulator.inclusive-gateway-default-flow.bpmn b/test/spec/simulator/Simulator.inclusive-gateway-default-flow.bpmn new file mode 100644 index 00000000..5648fbd5 --- /dev/null +++ b/test/spec/simulator/Simulator.inclusive-gateway-default-flow.bpmn @@ -0,0 +1,102 @@ + + + + + Flow_2 + + + Flow_1 + + + + + + + + Flow_2 + Flow_3 + Flow_4 + Flow_5 + + + Flow_3 + Flow_4 + Flow_5 + Flow_1 + + + FAKE + + + + FAKE + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/spec/simulator/Simulator.inclusive-gateway-default-flow.json b/test/spec/simulator/Simulator.inclusive-gateway-default-flow.json new file mode 100644 index 00000000..879309ab --- /dev/null +++ b/test/spec/simulator/Simulator.inclusive-gateway-default-flow.json @@ -0,0 +1,34 @@ +[ + "createScope:Process_1:null", + "signal:Process_1:B", + "createScope:START:B", + "signal:START:C", + "exit:START:C", + "createScope:Flow_2:B", + "destroyScope:START:C", + "enter:Flow_2:B", + "exit:Flow_2:D", + "createScope:F_GATE:B", + "destroyScope:Flow_2:D", + "enter:F_GATE:B", + "exit:F_GATE:E", + "createScope:Flow_4:B", + "destroyScope:F_GATE:E", + "enter:Flow_4:B", + "exit:Flow_4:F", + "createScope:J_GATE:B", + "destroyScope:Flow_4:F", + "enter:J_GATE:B", + "exit:J_GATE:G", + "createScope:Flow_1:B", + "destroyScope:J_GATE:G", + "enter:Flow_1:B", + "exit:Flow_1:H", + "createScope:END:B", + "destroyScope:Flow_1:H", + "enter:END:B", + "exit:END:I", + "destroyScope:END:I", + "exit:Process_1:B", + "destroyScope:Process_1:B" +] \ No newline at end of file diff --git a/test/spec/simulator/Simulator.inclusive-gateway-multiple-starts.bpmn b/test/spec/simulator/Simulator.inclusive-gateway-multiple-starts.bpmn new file mode 100644 index 00000000..468f7cc6 --- /dev/null +++ b/test/spec/simulator/Simulator.inclusive-gateway-multiple-starts.bpmn @@ -0,0 +1,115 @@ + + + + + Flow_2 + + + Flow_1 + + + + + + + + Flow_2 + Flow_3 + Flow_4 + Flow_5 + + + Flow_4 + Flow_5 + Flow_0j4wzlf + Flow_1 + + + Flow_3 + Flow_0j4wzlf + + + + FAKE + + + + FAKE + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/spec/simulator/Simulator.inclusive-gateway-multiple-starts.json b/test/spec/simulator/Simulator.inclusive-gateway-multiple-starts.json new file mode 100644 index 00000000..627837f3 --- /dev/null +++ b/test/spec/simulator/Simulator.inclusive-gateway-multiple-starts.json @@ -0,0 +1,66 @@ +[ + "createScope:Process_1:null", + "signal:Process_1:B", + "createScope:START:B", + "signal:START:C", + "exit:START:C", + "createScope:Flow_2:B", + "destroyScope:START:C", + "enter:Flow_2:B", + "exit:Flow_2:D", + "createScope:F_GATE:B", + "destroyScope:Flow_2:D", + "enter:F_GATE:B", + "exit:F_GATE:E", + "createScope:Flow_3:B", + "createScope:Flow_4:B", + "createScope:Flow_5:B", + "destroyScope:F_GATE:E", + "enter:Flow_3:B", + "enter:Flow_4:B", + "enter:Flow_5:B", + "exit:Flow_3:F", + "createScope:PausedActivity:B", + "destroyScope:Flow_3:F", + "exit:Flow_4:G", + "createScope:J_GATE:B", + "destroyScope:Flow_4:G", + "exit:Flow_5:H", + "createScope:J_GATE:B", + "destroyScope:Flow_5:H", + "enter:PausedActivity:B", + "enter:J_GATE:B", + "enter:J_GATE:B", + "createScope:Process_1:null", + "signal:Process_1:L", + "createScope:START:L", + "signal:START:M", + "exit:START:M", + "createScope:Flow_2:L", + "destroyScope:START:M", + "enter:Flow_2:L", + "exit:Flow_2:N", + "createScope:F_GATE:L", + "destroyScope:Flow_2:N", + "enter:F_GATE:L", + "exit:F_GATE:O", + "createScope:Flow_3:L", + "createScope:Flow_4:L", + "createScope:Flow_5:L", + "destroyScope:F_GATE:O", + "enter:Flow_3:L", + "enter:Flow_4:L", + "enter:Flow_5:L", + "exit:Flow_3:P", + "createScope:PausedActivity:L", + "destroyScope:Flow_3:P", + "exit:Flow_4:Q", + "createScope:J_GATE:L", + "destroyScope:Flow_4:Q", + "exit:Flow_5:R", + "createScope:J_GATE:L", + "destroyScope:Flow_5:R", + "enter:PausedActivity:L", + "enter:J_GATE:L", + "enter:J_GATE:L" +] \ No newline at end of file diff --git a/test/spec/simulator/Simulator.inclusive-gateway-single-token-pass-through.bpmn b/test/spec/simulator/Simulator.inclusive-gateway-single-token-pass-through.bpmn new file mode 100644 index 00000000..4bf34083 --- /dev/null +++ b/test/spec/simulator/Simulator.inclusive-gateway-single-token-pass-through.bpmn @@ -0,0 +1,60 @@ + + + + + Flow_2 + + + + Flow_3 + + + + Flow_1 + + + + Flow_2 + Flow_3 + Flow_1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/spec/simulator/Simulator.inclusive-gateway-single-token-pass-through.json b/test/spec/simulator/Simulator.inclusive-gateway-single-token-pass-through.json new file mode 100644 index 00000000..812ae0de --- /dev/null +++ b/test/spec/simulator/Simulator.inclusive-gateway-single-token-pass-through.json @@ -0,0 +1,26 @@ +[ + "createScope:Process_1:null", + "signal:Process_1:B", + "createScope:START:B", + "signal:START:C", + "exit:START:C", + "createScope:Flow_2:B", + "destroyScope:START:C", + "enter:Flow_2:B", + "exit:Flow_2:D", + "createScope:GATE:B", + "destroyScope:Flow_2:D", + "enter:GATE:B", + "exit:GATE:E", + "createScope:Flow_1:B", + "destroyScope:GATE:E", + "enter:Flow_1:B", + "exit:Flow_1:F", + "createScope:END:B", + "destroyScope:Flow_1:F", + "enter:END:B", + "exit:END:G", + "destroyScope:END:G", + "exit:Process_1:B", + "destroyScope:Process_1:B" +] \ No newline at end of file diff --git a/test/spec/simulator/Simulator.inclusive-gateway-sync.bpmn b/test/spec/simulator/Simulator.inclusive-gateway-sync.bpmn new file mode 100644 index 00000000..2560882f --- /dev/null +++ b/test/spec/simulator/Simulator.inclusive-gateway-sync.bpmn @@ -0,0 +1,102 @@ + + + + + Flow_2 + + + Flow_1 + + + + + + + + Flow_2 + Flow_3 + Flow_4 + Flow_5 + + + Flow_3 + Flow_4 + Flow_5 + Flow_1 + + + FAKE + + + + FAKE + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/spec/simulator/Simulator.inclusive-gateway-sync.json b/test/spec/simulator/Simulator.inclusive-gateway-sync.json new file mode 100644 index 00000000..72d41b52 --- /dev/null +++ b/test/spec/simulator/Simulator.inclusive-gateway-sync.json @@ -0,0 +1,41 @@ +[ + "createScope:Process_1:null", + "signal:Process_1:B", + "createScope:START:B", + "signal:START:C", + "exit:START:C", + "createScope:Flow_2:B", + "destroyScope:START:C", + "enter:Flow_2:B", + "exit:Flow_2:D", + "createScope:F_GATE:B", + "destroyScope:Flow_2:D", + "enter:F_GATE:B", + "exit:F_GATE:E", + "createScope:Flow_3:B", + "createScope:Flow_5:B", + "destroyScope:F_GATE:E", + "enter:Flow_3:B", + "enter:Flow_5:B", + "exit:Flow_3:F", + "createScope:J_GATE:B", + "destroyScope:Flow_3:F", + "exit:Flow_5:G", + "createScope:J_GATE:B", + "destroyScope:Flow_5:G", + "enter:J_GATE:B", + "enter:J_GATE:B", + "destroyScope:J_GATE:H", + "exit:J_GATE:I", + "createScope:Flow_1:B", + "destroyScope:J_GATE:I", + "enter:Flow_1:B", + "exit:Flow_1:J", + "createScope:END:B", + "destroyScope:Flow_1:J", + "enter:END:B", + "exit:END:K", + "destroyScope:END:K", + "exit:Process_1:B", + "destroyScope:Process_1:B" +] \ No newline at end of file diff --git a/test/spec/simulator/SimulatorSpec.js b/test/spec/simulator/SimulatorSpec.js index 45cda97b..82dc29e2 100644 --- a/test/spec/simulator/SimulatorSpec.js +++ b/test/spec/simulator/SimulatorSpec.js @@ -669,6 +669,103 @@ describe('simulator', function() { }); + describe('inclusive gateway', function() { + + verify('inclusive-gateway-sync', (fixture) => { + + // given + setConfig(element('F_GATE'), { + activeOutgoing: [ element('Flow_3'), element('Flow_5') ] + }); + + // when + trigger({ + element: element('START') + }); + + // then + expectTrace(fixture()); + }); + + + verify('inclusive-gateway-single-token-pass-through', (fixture) => { + + // when + trigger({ + element: element('START') + }); + + // then + expectTrace(fixture()); + }); + + + verify('inclusive-gateway-default-flow', (fixture) => { + + // given + setConfig(element('F_GATE'), { + activeOutgoing: [ element('Flow_4') ] + }); + + // when + trigger({ + element: element('START') + }); + + // then + expectTrace(fixture()); + }); + + + // verify that tokens from separate executions don't interfere with each other + verify('inclusive-gateway-multiple-starts', (fixture) => { + + // given + setConfig(element('F_GATE'), { + activeOutgoing: [ element('Flow_3'), element('Flow_4'), element('Flow_5') ] + }); + waitAtElement(element('PausedActivity')); + + // when + trigger({ + element: element('START') + }); + trigger({ + element: element('START') + }); + + // then + expectTrace(fixture()); + }); + + + // verify that inclusive gateway continues execution when token is consumed via boundary event + verify('inclusive-gateway-boundary-event', (fixture) => { + + // given + setConfig(element('F_GATE'), { + activeOutgoing: [ element('Flow_3'), element('Flow_4'), element('Flow_5') ] + }); + waitAtElement(element('PausedActivity')); + trigger({ + element: element('START') + }); + + // when + const boundaryEvent = element('TimerEvent'); + trigger({ + element: boundaryEvent, + scope: findScope({ + element: element('PausedActivity') + }) + }); + + // then + expectTrace(fixture()); + }); + }); + + describe('end event', function() { verify('end-event', (fixture) => {