diff --git a/test/fixtures/zeebe/filtering.bpmn b/test/fixtures/zeebe/filtering.bpmn
new file mode 100644
index 0000000..3660321
--- /dev/null
+++ b/test/fixtures/zeebe/filtering.bpmn
@@ -0,0 +1,82 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ declare: subFoo = globalFoo
+
+
+
+ declare: taskFoo = subFoo
+read: taskFoo
+
+
+
+ write: localResult (-> depending on taskFoo)
+
+
+
+ produce: taskResult = localResult
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/fixtures/zeebe/read-write.bpmn b/test/fixtures/zeebe/read-write.bpmn
new file mode 100644
index 0000000..6e7a31b
--- /dev/null
+++ b/test/fixtures/zeebe/read-write.bpmn
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ Write: approved
+
+
+
+ Read: approved
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/fixtures/zeebe/read-write.hierarchical.bpmn b/test/fixtures/zeebe/read-write.hierarchical.bpmn
new file mode 100644
index 0000000..586a784
--- /dev/null
+++ b/test/fixtures/zeebe/read-write.hierarchical.bpmn
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ Write: application.approved
+
+
+
+ Read: application.approved
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/fixtures/zeebe/used-variables.bpmn b/test/fixtures/zeebe/used-variables.bpmn
new file mode 100644
index 0000000..63face7
--- /dev/null
+++ b/test/fixtures/zeebe/used-variables.bpmn
@@ -0,0 +1,178 @@
+
+
+
+
+
+ SequenceFlow_2
+
+
+
+ SequenceFlow_2
+ SequenceFlow_3
+
+
+ SequenceFlow_3
+ SequenceFlow_1
+ SequenceFlow_4
+
+
+
+ =isAgeVerified
+
+
+
+ SequenceFlow_4
+ SequenceFlow_5
+
+
+ SequenceFlow_1
+ SequenceFlow_5
+ SequenceFlow_6
+
+
+
+
+ SequenceFlow_6
+ SequenceFlow_10
+ SequenceFlow_7
+
+
+ =isAgeVerified and is empty(checkResults)
+
+
+
+ SequenceFlow_10
+ SequenceFlow_9
+
+
+ SequenceFlow_9
+
+
+
+ SequenceFlow_7
+ SequenceFlow_8
+
+
+ SequenceFlow_8
+
+
+
+ This process models a typical pro-code scenario where the data model is implicit ("in code")
+
+* variables are only used, never written in the model
+* intelligence should indicate variables used
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/fixtures/zeebe/used-variables.scopes.bpmn b/test/fixtures/zeebe/used-variables.scopes.bpmn
new file mode 100644
index 0000000..e5da4a0
--- /dev/null
+++ b/test/fixtures/zeebe/used-variables.scopes.bpmn
@@ -0,0 +1,73 @@
+
+
+
+
+
+
+
+
+
+ Flow_03u12aa
+
+
+
+
+
+
+
+
+
+
+
+ Flow_03u12aa
+
+
+ Defines <approved> as local variable
+
+
+
+ Uses <approved> variable
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/spec/zeebe/ZeebeVariableResolver.spec.js b/test/spec/zeebe/ZeebeVariableResolver.spec.js
index f6e3536..541108c 100644
--- a/test/spec/zeebe/ZeebeVariableResolver.spec.js
+++ b/test/spec/zeebe/ZeebeVariableResolver.spec.js
@@ -31,6 +31,11 @@ import subprocessNoOutputMappingXML from 'test/fixtures/zeebe/sub-process.no-out
import longBrokenExpressionXML from 'test/fixtures/zeebe/long-broken-expression.bpmn';
import immediatelyBrokenExpressionXML from 'test/fixtures/zeebe/immediately-broken-expression.bpmn';
import typeResolutionXML from 'test/fixtures/zeebe/type-resolution.bpmn';
+import usedVariablesXML from 'test/fixtures/zeebe/used-variables.bpmn';
+import usedVariablesScopesXML from 'test/fixtures/zeebe/used-variables.scopes.bpmn';
+import readWriteXML from 'test/fixtures/zeebe/read-write.bpmn';
+import readWriteHierarchicalXML from 'test/fixtures/zeebe/read-write.hierarchical.bpmn';
+import filteringXML from 'test/fixtures/zeebe/filtering.bpmn';
import VariableProvider from 'lib/VariableProvider';
import { getInputOutput } from '../../../lib/base/util/ExtensionElementsUtil';
@@ -2602,6 +2607,324 @@ describe('ZeebeVariableResolver', function() {
});
+
+ describe('used variables - scopes', function() {
+
+ beforeEach(bootstrapModeler(usedVariablesScopesXML, {
+ additionalModules: [
+ ZeebeVariableResolverModule
+ ],
+ moddleExtensions: {
+ zeebe: ZeebeModdle
+ }
+ }));
+
+
+ it('should attach to local scope', inject(async function(elementRegistry, variableResolver) {
+
+ // given
+ const subProcess = elementRegistry.get('SubProcess_1');
+
+ // when
+ const variables = await variableResolver.getVariablesForElement(subProcess);
+
+ // then
+ expect(variables).to.variableEqual([
+ { name: 'taskResult' },
+ { name: 'approved', usedBy: [ 'Task_2' ] }
+ ]);
+ }));
+
+
+ it('should attach to global scope', inject(async function(elementRegistry, variableResolver) {
+
+ // given
+ const rootElement = elementRegistry.get('Process_1');
+
+ // when
+ const variables = await variableResolver.getVariablesForElement(rootElement);
+
+ // then
+ expect(variables).to.variableEqual([
+ { name: 'taskResult' },
+ { name: 'approved', usedBy: [ 'Task_1' ] }
+ ]);
+ }));
+
+ });
+
+
+ describe('used variables - pro code', function() {
+
+ beforeEach(bootstrapModeler(usedVariablesXML, {
+ additionalModules: [
+ ZeebeVariableResolverModule
+ ],
+ moddleExtensions: {
+ zeebe: ZeebeModdle
+ }
+ }));
+
+
+ it('should expose used variables globally', inject(async function(elementRegistry, variableResolver) {
+
+ // when
+ const allVariables = await variableResolver.getVariables();
+
+ // then
+ expect(allVariables).to.have.property('Process_1');
+
+ expect(allVariables['Process_1']).to.variableEqual([
+ { name: 'isAgeVerified', usedBy: [ 'SequenceFlow_1', 'SequenceFlow_10' ], scope: undefined, origin: undefined },
+ { name: 'subscriptionRequestID', usedBy: [ 'VerifyAgeTask' ], scope: undefined, origin: undefined },
+ { name: 'checkResults', usedBy: [ 'SequenceFlow_10' ], scope: undefined, origin: undefined }
+ ]);
+
+ // and when
+ const rootElement = elementRegistry.get('Process_1');
+
+ const processVariables = await variableResolver.getProcessVariables(rootElement);
+
+ expect(processVariables).to.eql(allVariables['Process_1']);
+ }));
+
+
+ describe('should expose used variables per element', function() {
+
+ it('sequence flow', inject(async function(elementRegistry, variableResolver) {
+
+ // when
+ const flow = elementRegistry.get('SequenceFlow_1');
+
+ const variables = await variableResolver.getVariablesForElement(flow);
+
+ // then
+ expect(variables).to.variableEqual([
+ { name: 'isAgeVerified', usedBy: [ 'SequenceFlow_1', 'SequenceFlow_10' ], scope: undefined, origin: undefined }
+ ]);
+ }));
+
+
+ it('receive task', inject(async function(elementRegistry, variableResolver) {
+
+ // when
+ const task = elementRegistry.get('VerifyAgeTask');
+
+ const variables = await variableResolver.getVariablesForElement(task);
+
+ // then
+ expect(variables).to.variableEqual([
+ { name: 'subscriptionRequestID', usedBy: [ 'VerifyAgeTask' ], scope: undefined, origin: undefined }
+ ]);
+ }));
+
+
+ it('process', inject(async function(elementRegistry, variableResolver) {
+
+ // when
+ const rootElement = elementRegistry.get('Process_1');
+
+ const variables = await variableResolver.getVariablesForElement(rootElement);
+
+ // then
+ // TODO(nikku): should that include variables not used by process?
+ expect(variables).to.variableEqual([]);
+ }));
+
+ });
+
+ });
+
+
+ describe('used variables - read and written', function() {
+
+ beforeEach(bootstrapModeler(readWriteXML, {
+ additionalModules: [
+ ZeebeVariableResolverModule
+ ],
+ moddleExtensions: {
+ zeebe: ZeebeModdle
+ }
+ }));
+
+
+ it('should indicate dual use', inject(async function(elementRegistry, variableResolver) {
+
+ // given
+ const task = elementRegistry.get('ValidateApprovedTask');
+
+ // when
+ const variables = await variableResolver.getVariablesForElement(task);
+
+ // then
+ expect(variables).to.variableEqual([
+ { name: 'approved', scope: 'Process_1', origin: [ 'ValidateApprovedTask' ], usedBy: [ 'ValidateApprovedTask' ] },
+ { name: 'localApproved', scope: 'ValidateApprovedTask' }
+ ]);
+ }));
+
+ });
+
+
+ describe('used variables - read and written / hierarchical', function() {
+
+ beforeEach(bootstrapModeler(readWriteHierarchicalXML, {
+ additionalModules: [
+ ZeebeVariableResolverModule
+ ],
+ moddleExtensions: {
+ zeebe: ZeebeModdle
+ }
+ }));
+
+
+ it('should indicate dual use', inject(async function(elementRegistry, variableResolver) {
+
+ // given
+ const task = elementRegistry.get('ValidateApprovedTask');
+
+ // when
+ const variables = await variableResolver.getVariablesForElement(task);
+
+ // then
+ expect(variables).to.variableEqual([
+ { name: 'application', scope: 'Process_1', origin: [ 'ValidateApprovedTask' ], usedBy: [ 'ValidateApprovedTask' ] },
+ { name: 'localApproved', scope: 'ValidateApprovedTask' }
+ ]);
+ }));
+
+ });
+
+
+ describe('filtering', function() {
+
+ beforeEach(bootstrapModeler(filteringXML, {
+ additionalModules: [
+ ZeebeVariableResolverModule
+ ],
+ moddleExtensions: {
+ zeebe: ZeebeModdle
+ }
+ }));
+
+
+
+ it('should NOT filter', inject(async function(elementRegistry, variableResolver) {
+
+ // given
+ const task = elementRegistry.get('Task_1');
+
+ // when
+ const variables = await variableResolver.getVariablesForElement(task);
+
+ // then
+ expect(variables).to.variableEqual([
+ { name: 'globalFoo', scope: undefined, usedBy: [ 'SubProcess_1' ] },
+ { name: 'subFoo', scope: 'SubProcess_1', origin: [ 'SubProcess_1' ], usedBy: [ 'Task_1' ] },
+ { name: 'taskFoo', scope: 'Task_1', origin: [ 'Task_1' ], usedBy: [ 'Task_1' ] },
+ { name: 'localResult', scope: 'Task_1', origin: [ 'Task_1' ], usedBy: [ 'Task_1' ] },
+ { name: 'taskResult', scope: 'Process_1', origin: [ 'Task_1' ] }
+ ]);
+ }));
+
+
+ it('should filter read', inject(async function(elementRegistry, variableResolver) {
+
+ // given
+ const task = elementRegistry.get('Task_1');
+
+ // when
+ const variables = await variableResolver.getVariablesForElement(task, { read: true });
+
+ // then
+ expect(variables).to.variableEqual([
+ { name: 'subFoo', scope: 'SubProcess_1', origin: [ 'SubProcess_1' ], usedBy: [ 'Task_1' ] },
+ { name: 'taskFoo', scope: 'Task_1', origin: [ 'Task_1' ], usedBy: [ 'Task_1' ] },
+ { name: 'localResult', scope: 'Task_1', origin: [ 'Task_1' ], usedBy: [ 'Task_1' ] }
+ ]);
+ }));
+
+
+ it('should filter read / local', inject(async function(elementRegistry, variableResolver) {
+
+ // given
+ const task = elementRegistry.get('Task_1');
+
+ // when
+ const variables = await variableResolver.getVariablesForElement(task, { read: true, local: true });
+
+ // then
+ expect(variables).to.variableEqual([
+ { name: 'taskFoo', scope: 'Task_1', origin: [ 'Task_1' ], usedBy: [ 'Task_1' ] },
+ { name: 'localResult', scope: 'Task_1', origin: [ 'Task_1' ], usedBy: [ 'Task_1' ] }
+ ]);
+ }));
+
+
+ it('should filter read / global', inject(async function(elementRegistry, variableResolver) {
+
+ // given
+ const task = elementRegistry.get('Task_1');
+
+ // when
+ const variables = await variableResolver.getVariablesForElement(task, { read: true, local: false });
+
+ // then
+ expect(variables).to.variableEqual([
+ { name: 'subFoo', scope: 'SubProcess_1', origin: [ 'SubProcess_1' ], usedBy: [ 'Task_1' ] }
+ ]);
+ }));
+
+
+ it('should filter write', inject(async function(elementRegistry, variableResolver) {
+
+ // given
+ const task = elementRegistry.get('Task_1');
+
+ // when
+ const variables = await variableResolver.getVariablesForElement(task, { written: true });
+
+ // then
+ expect(variables).to.variableEqual([
+ { name: 'taskFoo', scope: 'Task_1', origin: [ 'Task_1' ], usedBy: [ 'Task_1' ] },
+ { name: 'localResult', scope: 'Task_1', origin: [ 'Task_1' ], usedBy: [ 'Task_1' ] },
+ { name: 'taskResult', scope: 'Process_1', origin: [ 'Task_1' ] }
+ ]);
+ }));
+
+
+ it('should filter written / local', inject(async function(elementRegistry, variableResolver) {
+
+ // given
+ const task = elementRegistry.get('Task_1');
+
+ // when
+ const variables = await variableResolver.getVariablesForElement(task, { written: true, local: true });
+
+ // then
+ expect(variables).to.variableEqual([
+ { name: 'taskFoo', scope: 'Task_1', origin: [ 'Task_1' ], usedBy: [ 'Task_1' ] },
+ { name: 'localResult', scope: 'Task_1', origin: [ 'Task_1' ], usedBy: [ 'Task_1' ] }
+ ]);
+ }));
+
+
+ it('should filter written / global', inject(async function(elementRegistry, variableResolver) {
+
+ // given
+ const task = elementRegistry.get('Task_1');
+
+ // when
+ const variables = await variableResolver.getVariablesForElement(task, { written: true, local: false });
+
+ // then
+ expect(variables).to.variableEqual([
+ { name: 'taskResult', scope: 'Process_1', origin: [ 'Task_1' ] }
+ ]);
+ }));
+
+ });
+
});
// helpers //////////////////////