diff --git a/rules/camunda-cloud/user-task-form.js b/rules/camunda-cloud/user-task-form.js
index 30c588ca..fca0c2a1 100644
--- a/rules/camunda-cloud/user-task-form.js
+++ b/rules/camunda-cloud/user-task-form.js
@@ -14,7 +14,7 @@ const { skipInNonExecutableProcess } = require('../utils/rule');
const { greaterOrEqual } = require('../utils/version');
-const formIdAllowedVersion = '8.3';
+const formIdAllowedVersion = '8.4';
module.exports = skipInNonExecutableProcess(function({ version }) {
function check(node, reporter) {
diff --git a/rules/utils/element.js b/rules/utils/element.js
index beb5aa3b..ea25e0e8 100644
--- a/rules/utils/element.js
+++ b/rules/utils/element.js
@@ -4,6 +4,8 @@ const {
isFunction,
isNil,
isObject,
+ isString,
+ isUndefined,
some
} = require('min-dash');
@@ -141,7 +143,7 @@ module.exports.hasProperties = function(node, properties, parentNode = null) {
const propertyValue = node.get(propertyName);
- if (propertyChecks.required && !propertyValue) {
+ if (propertyChecks.required && isEmptyValue(propertyValue)) {
return [
...results,
{
@@ -164,7 +166,7 @@ module.exports.hasProperties = function(node, properties, parentNode = null) {
if (propertyChecks.dependentRequired) {
const dependency = node.get(propertyChecks.dependentRequired);
- if (dependency && !propertyValue) {
+ if (dependency && isEmptyValue(propertyValue)) {
return [
...results,
{
@@ -303,7 +305,9 @@ function findProperties(node, propertyNames) {
const properties = [];
for (const propertyName of propertyNames) {
- if (isDefined(node.get(propertyName))) {
+ const propertyValue = node.get(propertyName);
+
+ if (!isEmptyValue(propertyValue)) {
properties.push(node.get(propertyName));
}
}
@@ -470,4 +474,12 @@ function findParent(node, type) {
return findParent(parent, type);
}
-module.exports.findParent = findParent;
\ No newline at end of file
+module.exports.findParent = findParent;
+
+function isEmptyString(value) {
+ return isString(value) && value.trim() === '';
+}
+
+function isEmptyValue(value) {
+ return isUndefined(value) || isNil(value) || isEmptyString(value);
+}
\ No newline at end of file
diff --git a/test/camunda-cloud/integration/user-task-form-form-id-errors.bpmn b/test/camunda-cloud/integration/user-task-form-form-id-errors.bpmn
new file mode 100644
index 00000000..a78d43f2
--- /dev/null
+++ b/test/camunda-cloud/integration/user-task-form-form-id-errors.bpmn
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/camunda-cloud/integration/user-task-form-form-id.bpmn b/test/camunda-cloud/integration/user-task-form-form-id.bpmn
new file mode 100644
index 00000000..d8d89622
--- /dev/null
+++ b/test/camunda-cloud/integration/user-task-form-form-id.bpmn
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/camunda-cloud/integration/user-task-form-errors.bpmn b/test/camunda-cloud/integration/user-task-form-form-key-errors.bpmn
similarity index 100%
rename from test/camunda-cloud/integration/user-task-form-errors.bpmn
rename to test/camunda-cloud/integration/user-task-form-form-key-errors.bpmn
diff --git a/test/camunda-cloud/integration/user-task-form.bpmn b/test/camunda-cloud/integration/user-task-form-form-key.bpmn
similarity index 100%
rename from test/camunda-cloud/integration/user-task-form.bpmn
rename to test/camunda-cloud/integration/user-task-form-form-key.bpmn
diff --git a/test/camunda-cloud/integration/user-task-form.spec.js b/test/camunda-cloud/integration/user-task-form.spec.js
index 7a691ac7..e40cf312 100644
--- a/test/camunda-cloud/integration/user-task-form.spec.js
+++ b/test/camunda-cloud/integration/user-task-form.spec.js
@@ -6,19 +6,75 @@ const NodeResolver = require('bpmnlint/lib/resolver/node-resolver');
const { readModdle } = require('../../helper');
-const versions = [
- '1.0',
- '1.1',
- '1.2',
- '1.3',
- '8.0',
- '8.1',
- '8.2'
-];
-
describe('integration - user-task-form', function() {
- versions.forEach(function(version) {
+ [
+ '1.0',
+ '1.1',
+ '1.2',
+ '1.3',
+ '8.0',
+ '8.1',
+ '8.2',
+ '8.3',
+ '8.4'
+ ].forEach(function(version) {
+
+ let linter;
+
+ beforeEach(function() {
+ linter = new Linter({
+ config: {
+ extends: `plugin:camunda-compat/camunda-cloud-${ version.replace('.', '-') }`
+ },
+ resolver: new NodeResolver()
+ });
+ });
+
+
+ describe(`Camunda Cloud ${ version } (form key)`, function() {
+
+ describe('no errors', function() {
+
+ it('should not have errors', async function() {
+
+ // given
+ const { root } = await readModdle('test/camunda-cloud/integration/user-task-form-form-key.bpmn');
+
+ // when
+ const reports = await linter.lint(root);
+
+ // then
+ expect(reports[ 'camunda-compat/user-task-form' ]).not.to.exist;
+ });
+
+ });
+
+
+ describe('errors', function() {
+
+ it('should have errors', async function() {
+
+ // given
+ const { root } = await readModdle('test/camunda-cloud/integration/user-task-form-form-key-errors.bpmn');
+
+ // when
+ const reports = await linter.lint(root);
+
+ // then
+ expect(reports[ 'camunda-compat/user-task-form' ]).to.exist;
+ });
+
+ });
+
+ });
+
+ });
+
+
+ [
+ '8.4'
+ ].forEach(function(version) {
let linter;
@@ -32,14 +88,14 @@ describe('integration - user-task-form', function() {
});
- describe(`Camunda Cloud ${ version }`, function() {
+ describe(`Camunda Cloud ${ version } (form ID)`, function() {
describe('no errors', function() {
it('should not have errors', async function() {
// given
- const { root } = await readModdle('test/camunda-cloud/integration/user-task-form.bpmn');
+ const { root } = await readModdle('test/camunda-cloud/integration/user-task-form-form-id.bpmn');
// when
const reports = await linter.lint(root);
@@ -56,7 +112,7 @@ describe('integration - user-task-form', function() {
it('should have errors', async function() {
// given
- const { root } = await readModdle('test/camunda-cloud/integration/user-task-form-errors.bpmn');
+ const { root } = await readModdle('test/camunda-cloud/integration/user-task-form-form-id-errors.bpmn');
// when
const reports = await linter.lint(root);
diff --git a/test/camunda-cloud/user-task-form.spec.js b/test/camunda-cloud/user-task-form.spec.js
index 5a81abfe..46f98f15 100644
--- a/test/camunda-cloud/user-task-form.spec.js
+++ b/test/camunda-cloud/user-task-form.spec.js
@@ -13,7 +13,7 @@ const { ERROR_TYPES } = require('../../rules/utils/element');
const valid = [
{
name: 'user task (user task form)',
- config: { version: '8.2' },
+ config: { version: '8.3' },
moddleElement: createModdle(createProcess(`
{}
@@ -27,14 +27,14 @@ const valid = [
},
{
name: 'user task',
- config: { version: '8.2' },
+ config: { version: '8.3' },
moddleElement: createModdle(createProcess(`
`))
},
{
name: 'user task (no form key) (non-executable process)',
- config: { version: '8.2' },
+ config: { version: '8.3' },
moddleElement: createModdle(createDefinitions(`
@@ -49,8 +49,8 @@ const valid = [
const invalid = [
{
- name: 'user task (no form key)',
- config: { version: '8.2' },
+ name: 'user task (no form key) (Camunda 8.3)',
+ config: { version: '8.3' },
moddleElement: createModdle(createProcess(`
@@ -76,8 +76,35 @@ const invalid = [
}
},
{
- name: 'user task (no form key or form ID)',
+ name: 'user task (empty form key) (Camunda 8.3)',
config: { version: '8.3' },
+ moddleElement: createModdle(createProcess(`
+
+
+
+
+
+ `)),
+ report: {
+ id: 'UserTask_1',
+ message: 'Element of type must have property ',
+ path: [
+ 'extensionElements',
+ 'values',
+ 0,
+ 'formKey'
+ ],
+ data: {
+ type: ERROR_TYPES.PROPERTY_REQUIRED,
+ node: 'zeebe:FormDefinition',
+ parentNode: 'UserTask_1',
+ requiredProperty: 'formKey'
+ }
+ }
+ },
+ {
+ name: 'user task (no form key or form ID) (Camunda 8.4)',
+ config: { version: '8.4' },
moddleElement: createModdle(createProcess(`
@@ -105,8 +132,66 @@ const invalid = [
}
},
{
- name: 'user task (form ID with Camunda 8.2)',
- config: { version: '8.2' },
+ name: 'user task (empty form key) (Camunda 8.4)',
+ config: { version: '8.4' },
+ moddleElement: createModdle(createProcess(`
+
+
+
+
+
+ `)),
+ report: {
+ id: 'UserTask_1',
+ message: 'Element of type must have property or ',
+ path: [
+ 'extensionElements',
+ 'values',
+ 0
+ ],
+ data: {
+ type: ERROR_TYPES.PROPERTY_REQUIRED,
+ node: 'zeebe:FormDefinition',
+ parentNode: 'UserTask_1',
+ requiredProperty: [
+ 'formKey',
+ 'formId'
+ ]
+ }
+ }
+ },
+ {
+ name: 'user task (empty form ID) (Camunda 8.4)',
+ config: { version: '8.4' },
+ moddleElement: createModdle(createProcess(`
+
+
+
+
+
+ `)),
+ report: {
+ id: 'UserTask_1',
+ message: 'Element of type must have property or ',
+ path: [
+ 'extensionElements',
+ 'values',
+ 0
+ ],
+ data: {
+ type: ERROR_TYPES.PROPERTY_REQUIRED,
+ node: 'zeebe:FormDefinition',
+ parentNode: 'UserTask_1',
+ requiredProperty: [
+ 'formKey',
+ 'formId'
+ ]
+ }
+ }
+ },
+ {
+ name: 'user task (form ID) (Camunda 8.3)',
+ config: { version: '8.3' },
moddleElement: createModdle(createProcess(`
@@ -116,7 +201,7 @@ const invalid = [
`)),
report: {
id: 'UserTask_1',
- message: 'Property only allowed by Camunda 8.3 or newer',
+ message: 'Property only allowed by Camunda 8.4 or newer',
path: [
'extensionElements',
'values',
@@ -128,13 +213,13 @@ const invalid = [
node: 'zeebe:FormDefinition',
parentNode: 'UserTask_1',
property: 'formId',
- allowedVersion: '8.3'
+ allowedVersion: '8.4'
}
}
},
{
- name: 'user task (form key and form ID)',
- config: { version: '8.3' },
+ name: 'user task (form key and form ID) (Camunda 8.4)',
+ config: { version: '8.4' },
moddleElement: createModdle(createProcess(`
@@ -162,8 +247,8 @@ const invalid = [
}
},
{
- name: 'user task (empty user task form)',
- config: { version: '8.2' },
+ name: 'user task (empty user task form) (Camunda 8.3)',
+ config: { version: '8.3' },
moddleElement: createModdle(createProcess(`
diff --git a/test/utils/element.spec.js b/test/utils/element.spec.js
index 4636f085..16bd82ac 100644
--- a/test/utils/element.spec.js
+++ b/test/utils/element.spec.js
@@ -552,6 +552,36 @@ describe('utils/element', function() {
}
]);
});
+
+
+ it('should return errors (property is empty string)', function() {
+
+ // given
+ const formDefinition = createElement('zeebe:FormDefinition', {
+ formKey: ''
+ });
+
+ // when
+ const errors = hasProperty(formDefinition, [ 'formKey', 'formId' ]);
+
+ // then
+ expect(errors).to.eql([
+ {
+ message: 'Element of type must have property or ',
+ path: null,
+ data: {
+ type: ERROR_TYPES.PROPERTY_REQUIRED,
+ node: formDefinition,
+ parentNode: null,
+ requiredProperty: [
+ 'formKey',
+ 'formId'
+ ]
+ }
+ }
+ ]);
+ });
+
});
@@ -578,7 +608,7 @@ describe('utils/element', function() {
});
- it('should return errors', function() {
+ it('should return errors (undefined)', function() {
// given
const taskDefinition = createElement('zeebe:TaskDefinition');
@@ -607,6 +637,38 @@ describe('utils/element', function() {
]);
});
+
+ it('should return errors (empty string)', function() {
+
+ // given
+ const taskDefinition = createElement('zeebe:TaskDefinition', {
+ type: ''
+ });
+
+ // when
+ const errors = hasProperties(taskDefinition, {
+ type: {
+ required: true
+ }
+ });
+
+ // then
+ expect(errors).eql([
+ {
+ message: 'Element of type must have property ',
+ path: [
+ 'type'
+ ],
+ data: {
+ type: ERROR_TYPES.PROPERTY_REQUIRED,
+ node: taskDefinition,
+ parentNode: null,
+ requiredProperty: 'type'
+ }
+ }
+ ]);
+ });
+
});
@@ -632,7 +694,7 @@ describe('utils/element', function() {
});
- it('should return errors', function() {
+ it('should return errors (undefined)', function() {
// given
const loopCharacteristics = createElement('zeebe:LoopCharacteristics', {
@@ -664,6 +726,40 @@ describe('utils/element', function() {
]);
});
+
+ it('should return errors (empty string)', function() {
+
+ // given
+ const loopCharacteristics = createElement('zeebe:LoopCharacteristics', {
+ outputCollection: 'foo',
+ outputElement: ''
+ });
+
+ // when
+ const errors = hasProperties(loopCharacteristics, {
+ outputElement: {
+ dependentRequired: 'outputCollection'
+ }
+ });
+
+ // then
+ expect(errors).eql([
+ {
+ message: 'Element of type must have property if it has property ',
+ path: [
+ 'outputElement'
+ ],
+ data: {
+ type: ERROR_TYPES.PROPERTY_DEPENDENT_REQUIRED,
+ node: loopCharacteristics,
+ parentNode: null,
+ property: 'outputCollection',
+ dependentRequiredProperty: 'outputElement'
+ }
+ }
+ ]);
+ });
+
});