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' + } + } + ]); + }); + });