diff --git a/package-lock.json b/package-lock.json index 01de95e1..02a4c776 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "min-dash": "^4.0.0", "min-dom": "^4.0.3", "preact-markup": "^2.1.1", + "semver": "^7.6.3", "semver-compare": "^1.0.0", "uuid": "^11.0.0" }, @@ -163,6 +164,15 @@ "url": "https://opencollective.com/babel" } }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/@babel/generator": { "version": "7.26.2", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.2.tgz", @@ -209,6 +219,15 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/@babel/helper-module-imports": { "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", @@ -1192,19 +1211,6 @@ "node": ">=12" } }, - "node_modules/@puppeteer/browsers/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@puppeteer/browsers/node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", @@ -2590,18 +2596,6 @@ "node": ">=10" } }, - "node_modules/babel-plugin-istanbul/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -4336,6 +4330,15 @@ "node": ">=0.10.0" } }, + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/eslint-plugin-mocha": { "version": "10.5.0", "resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-10.5.0.tgz", @@ -4444,6 +4447,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/eslint-plugin-react/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/eslint-scope": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", @@ -6261,6 +6273,15 @@ "node": ">=8" } }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/istanbul-lib-report": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", @@ -6983,6 +7004,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -8843,12 +8873,14 @@ "dev": true }, "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "bin": { "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, "node_modules/semver-compare": { @@ -10330,6 +10362,14 @@ "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" + }, + "dependencies": { + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true + } } }, "@babel/generator": { @@ -10365,6 +10405,14 @@ "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" + }, + "dependencies": { + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true + } } }, "@babel/helper-module-imports": { @@ -11107,12 +11155,6 @@ "wrap-ansi": "^7.0.0" } }, - "semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true - }, "yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", @@ -12114,12 +12156,6 @@ "istanbul-lib-coverage": "^3.2.0", "semver": "^7.5.4" } - }, - "semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true } } }, @@ -13510,6 +13546,12 @@ "requires": { "esutils": "^2.0.2" } + }, + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true } } }, @@ -13580,6 +13622,12 @@ "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" } + }, + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true } } }, @@ -14750,6 +14798,14 @@ "@istanbuljs/schema": "^0.1.2", "istanbul-lib-coverage": "^3.2.0", "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true + } } }, "istanbul-lib-report": { @@ -15325,6 +15381,14 @@ "dev": true, "requires": { "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true + } } }, "media-typer": { @@ -16671,10 +16735,9 @@ } }, "semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==" }, "semver-compare": { "version": "1.0.0", diff --git a/package.json b/package.json index b4610ea0..c946e55d 100644 --- a/package.json +++ b/package.json @@ -68,6 +68,7 @@ "min-dash": "^4.0.0", "min-dom": "^4.0.3", "preact-markup": "^2.1.1", + "semver": "^7.6.3", "semver-compare": "^1.0.0", "uuid": "^11.0.0" }, diff --git a/src/cloud-element-templates/ElementTemplates.js b/src/cloud-element-templates/ElementTemplates.js index 08df40ea..2fecc163 100644 --- a/src/cloud-element-templates/ElementTemplates.js +++ b/src/cloud-element-templates/ElementTemplates.js @@ -9,8 +9,8 @@ import { default as DefaultElementTemplates } from '../element-templates/Element * Registry for element templates. */ export default class ElementTemplates extends DefaultElementTemplates { - constructor(templateElementFactory, commandStack, eventBus, modeling, injector) { - super(commandStack, eventBus, modeling, injector); + constructor(templateElementFactory, commandStack, eventBus, modeling, injector, config) { + super(commandStack, eventBus, modeling, injector, config); this._templateElementFactory = templateElementFactory; } @@ -75,6 +75,7 @@ export default class ElementTemplates extends DefaultElementTemplates { return context.element; } + } ElementTemplates.$inject = [ @@ -82,5 +83,6 @@ ElementTemplates.$inject = [ 'commandStack', 'eventBus', 'modeling', - 'injector' + 'injector', + 'config.elementTemplates', ]; diff --git a/src/cloud-element-templates/ElementTemplatesLoader.js b/src/cloud-element-templates/ElementTemplatesLoader.js index 50c57c5e..1f9df917 100644 --- a/src/cloud-element-templates/ElementTemplatesLoader.js +++ b/src/cloud-element-templates/ElementTemplatesLoader.js @@ -28,10 +28,8 @@ export default class ElementTemplatesLoader extends TemplatesLoader { elementTemplates.set(validTemplates); if (errors.length) { - this.templateErrors(errors); + this._templateErrors(errors); } - - this.templatesChanged(); } } diff --git a/src/cloud-element-templates/Validator.js b/src/cloud-element-templates/Validator.js index a2b95189..8b51d3ea 100644 --- a/src/cloud-element-templates/Validator.js +++ b/src/cloud-element-templates/Validator.js @@ -6,11 +6,16 @@ import { import semverCompare from 'semver-compare'; +import { + validRange as isSemverRangeValid +} from 'semver'; + import { validateZeebe as validateAgainstSchema, getZeebeSchemaPackage as getTemplateSchemaPackage, getZeebeSchemaVersion as getTemplateSchemaVersion } from '@bpmn-io/element-templates-validator'; +import { forEach } from 'min-dash'; const SUPPORTED_SCHEMA_VERSION = getTemplateSchemaVersion(); const SUPPORTED_SCHEMA_PACKAGE = getTemplateSchemaPackage(); @@ -31,8 +36,6 @@ export class Validator extends BaseValidator { * @return {Error} validation error, if any */ _validateTemplate(template) { - let err; - const id = template.id, version = template.version || '_', schema = template.$schema, @@ -78,25 +81,48 @@ export class Validator extends BaseValidator { } // (5) JSON schema compliance - const validationResult = validateAgainstSchema(template); + const schemaValidationResult = validateAgainstSchema(template); const { - errors, + errors: schemaErrors, valid - } = validationResult; + } = schemaValidationResult; if (!valid) { - err = new Error('invalid template'); - - filteredSchemaErrors(errors).forEach((error) => { + filteredSchemaErrors(schemaErrors).forEach((error) => { this._logError(error.message, template); }); + + return new Error('invalid template'); } - return err; + // (6) engines validation + const enginesError = this._validateEngines(template); + + if (enginesError) { + return enginesError; + } + + return null; } isSchemaValid(schema) { return schema && schema.includes(SUPPORTED_SCHEMA_PACKAGE); } + + _validateEngines(template) { + + let err; + + forEach(template.engines, (rangeStr, engine) => { + + if (!isSemverRangeValid(rangeStr)) { + err = this._logError(new Error( + `Engine <${engine}> specifies invalid semver range <${rangeStr}>` + ), template); + } + }); + + return err; + } } diff --git a/src/element-templates/ElementTemplates.js b/src/element-templates/ElementTemplates.js index 754bfaaf..38ea80b6 100644 --- a/src/element-templates/ElementTemplates.js +++ b/src/element-templates/ElementTemplates.js @@ -2,10 +2,12 @@ import { filter, find, flatten, + has, isNil, isObject, isString, isUndefined, + reduce, values } from 'min-dash'; @@ -16,17 +18,32 @@ import { import { isAny } from 'bpmn-js/lib/util/ModelUtil'; +import { + valid as isSemverValid, + satisfies as isSemverCompatible, + coerce +} from 'semver'; + /** * Registry for element templates. */ export default class ElementTemplates { - constructor(commandStack, eventBus, modeling, injector) { + constructor(commandStack, eventBus, modeling, injector, config) { this._commandStack = commandStack; this._eventBus = eventBus; this._injector = injector; this._modeling = modeling; this._templatesById = {}; + this._templates = []; + + config = config || {}; + + this._engines = this._coerceEngines(config.engines || {}); + + eventBus.on('elementTemplates.engines.changed', event => { + this.set(this._templates); + }); } /** @@ -109,24 +126,94 @@ export default class ElementTemplates { */ set(templates) { this._templatesById = {}; + this._templates = templates; templates.forEach((template) => { - const id = template.id, - version = isUndefined(template.version) ? '_' : template.version; + const id = template.id; + const version = isUndefined(template.version) ? '_' : template.version; if (!this._templatesById[ id ]) { - this._templatesById[ id ] = { - latest: template - }; + this._templatesById[ id ] = { }; } this._templatesById[ id ][ version ] = template; - const latestVerions = this._templatesById[ id ].latest.version; - if (isUndefined(latestVerions) || template.version > latestVerions) { - this._templatesById[ id ].latest = template; + const latest = this._templatesById[ id ].latest; + + if (this.isCompatible(template)) { + if (!latest || isUndefined(latest.version) || latest.version < version) { + this._templatesById[ id ].latest = template; + } } }); + + this._fire('changed'); + } + + getEngines() { + return this._engines; + } + + setEngines(engines) { + this._engines = this._coerceEngines(engines); + + this._fire('engines.changed'); + } + + /** + * Ensures that only valid engines are kept around + * + * @param { Record } engines + * + * @return { Record } filtered, valid engines + */ + _coerceEngines(engines) { + + return reduce(engines, (validEngines, version, engine) => { + + const coercedVersion = coerce(version); + + if (!isSemverValid(coercedVersion)) { + console.error( + new Error(`Engine <${ engine }> specifies unparseable version <${version}>`) + ); + + return validEngines; + } + + return { + ...validEngines, + [ engine ]: coercedVersion.raw + }; + }, {}); + } + + /** + * Check if template is compatible with currently set engine version. + * + * @param {ElementTemplate} template + * + * @return {boolean} - true if compatible or no engine is set for elementTemplates or template. + */ + isCompatible(template) { + const localEngines = this._engines; + const templateEngines = template.engines; + + for (const engine in templateEngines) { + + // we check compatibility against all locally provided + // engines, hence computing the overlap here. + + if (!has(localEngines, engine)) { + continue; + } + + if (!isSemverCompatible(localEngines[engine], templateEngines[engine])) { + return false; + } + } + + return true; } /** @@ -138,15 +225,15 @@ export default class ElementTemplates { _getTemplateVerions(id, options = {}) { const { - latest: latestOnly, + latest: includeLatestOnly, deprecated: includeDeprecated } = options; const templatesById = this._templatesById; const getVersions = (template) => { const { latest, ...versions } = template; - return latestOnly ? ( - !includeDeprecated && latest.deprecated ? [] : [ latest ] + return includeLatestOnly ? ( + !includeDeprecated && (latest && latest.deprecated) ? [] : (latest ? [ latest ] : []) ) : values(versions) ; }; @@ -208,11 +295,15 @@ export default class ElementTemplates { this._commandStack.execute('propertiesPanel.camunda.changeTemplate', context); - this._eventBus.fire(`elementTemplates.${action}`, payload); + this._fire(action, payload); return context.element; } + _fire(action, payload) { + return this._eventBus.fire(`elementTemplates.${action}`, payload); + } + /** * Remove template from a given element. * @@ -221,9 +312,7 @@ export default class ElementTemplates { * @return {djs.model.Base} the updated element */ removeTemplate(element) { - const eventBus = this._injector.get('eventBus'); - - eventBus.fire('elementTemplates.remove', { element }); + this._fire('remove', { element }); const context = { element @@ -252,7 +341,6 @@ ElementTemplates.$inject = [ 'commandStack', 'eventBus', 'modeling', - 'injector' -]; - - + 'injector', + 'config.elementTemplates' +]; \ No newline at end of file diff --git a/src/element-templates/ElementTemplatesLoader.js b/src/element-templates/ElementTemplatesLoader.js index b67eb14f..f4c74c3d 100644 --- a/src/element-templates/ElementTemplatesLoader.js +++ b/src/element-templates/ElementTemplatesLoader.js @@ -32,7 +32,7 @@ export default class ElementTemplatesLoader { } if (config && config.loadTemplates) { - this._loadTemplates = config.templates; + this._loadTemplates = config.loadTemplates; } eventBus.on('diagram.init', () => { @@ -54,7 +54,7 @@ export default class ElementTemplatesLoader { return loadTemplates((err, templates) => { if (err) { - return this.templateErrors([ err ]); + return this._templateErrors([ err ]); } this.setTemplates(templates); @@ -79,18 +79,12 @@ export default class ElementTemplatesLoader { elementTemplates.set(validTemplates); if (errors.length) { - this.templateErrors(errors); + this._templateErrors(errors); } - - this.templatesChanged(); - } - - templatesChanged() { - this._eventBus.fire('elementTemplates.changed'); } - templateErrors(errors) { - this._eventBus.fire('elementTemplates.errors', { + _templateErrors(errors) { + this._elementTemplates._fire('errors', { errors: errors }); } diff --git a/src/element-templates/Validator.js b/src/element-templates/Validator.js index 5f31e7b7..87db86f4 100644 --- a/src/element-templates/Validator.js +++ b/src/element-templates/Validator.js @@ -1,11 +1,16 @@ import { filter, + forEach, isArray, isString } from 'min-dash'; import semverCompare from 'semver-compare'; +import { + validRange as isSemverRangeValid +} from 'semver'; + import { validate as validateAgainstSchema, getSchemaVersion as getTemplateSchemaVersion @@ -14,6 +19,7 @@ import { const SUPPORTED_SCHEMA_VERSION = getTemplateSchemaVersion(); const MORPHABLE_TYPES = [ 'bpmn:Activity', 'bpmn:Event', 'bpmn:Gateway' ]; + /** * A element template validator. */ @@ -79,8 +85,6 @@ export class Validator { * @return {Error} validation error, if any */ _validateTemplate(template) { - let err; - const id = template.id, version = template.version || '_', schemaVersion = template.$schema && getSchemaVersion(template.$schema); @@ -110,21 +114,44 @@ export class Validator { } // (4) JSON schema compliance - const validationResult = validateAgainstSchema(template); + const schemaValidationResult = validateAgainstSchema(template); const { - errors, + errors: schemaErrors, valid - } = validationResult; + } = schemaValidationResult; if (!valid) { - err = new Error('invalid template'); - - filteredSchemaErrors(errors).forEach((error) => { + filteredSchemaErrors(schemaErrors).forEach((error) => { this._logError(error.message, template); }); + + return new Error('invalid template'); + } + + // (5) engines validation + const enginesError = this._validateEngines(template); + + if (enginesError) { + return enginesError; } + return null; + } + + _validateEngines(template) { + + let err; + + forEach(template.engines, (rangeStr, engine) => { + + if (!isSemverRangeValid(rangeStr)) { + err = this._logError(new Error( + `Engine <${engine}> specifies invalid semver range <${rangeStr}>` + ), template); + } + }); + return err; } diff --git a/test/spec/cloud-element-templates/ElementTemplates.engines-templates.json b/test/spec/cloud-element-templates/ElementTemplates.engines-templates.json new file mode 100644 index 00000000..da5ffbb9 --- /dev/null +++ b/test/spec/cloud-element-templates/ElementTemplates.engines-templates.json @@ -0,0 +1,82 @@ +[ + { + "id": "example.engines.test.multiple", + "name": " Test - Multiple", + "description": "does not match if { desktopModeler: >=1 } is provided", + "version": 2, + "engines": { + "camunda": "^8.6", + "webModeler": "^4.1", + "desktopModeler": "^0" + }, + "appliesTo": [ + "bpmn:Task" + ], + "properties": [] + }, + { + "id": "example.engines.test.multiple", + "name": " Test - Multiple", + "description": "matches if { camunda: ^8.6, webModeler: ^4.1 } engine is indicated, or properties are not provided", + "version": 1, + "engines": { + "camunda": "^8.6", + "webModeler": "^4.1" + }, + "appliesTo": [ + "bpmn:Task" + ], + "properties": [] + }, + + { + "id": "example.engines.test.basic", + "name": " Test - Basic", + "description": "matches if { camunda: ^8.6 }, or if no engine is provided", + "version": 3, + "engines": { + "camunda": "^8.6" + }, + "appliesTo": [ + "bpmn:Task" + ], + "properties": [] + }, + { + "id": "example.engines.test.basic", + "name": " Test - Basic", + "description": "matches if { camunda: ^8.5 }, or if no engine is provided", + "version": 2, + "engines": { + "camunda": "^8.5" + }, + "appliesTo": [ + "bpmn:Task" + ], + "properties": [] + }, + { + "id": "example.engines.test.basic", + "name": " Test - Basic", + "description": "always matches", + "version": 1, + "appliesTo": [ + "bpmn:Task" + ], + "properties": [] + }, + + { + "id": "example.engines.test.broken", + "name": " Test - broken Semver range", + "description": "specifies broken semver range", + "version": 1, + "engines": { + "camunda": "invalid-version" + }, + "appliesTo": [ + "bpmn:Task" + ], + "properties": [] + } +] \ No newline at end of file diff --git a/test/spec/cloud-element-templates/ElementTemplates.spec.js b/test/spec/cloud-element-templates/ElementTemplates.spec.js index 0fb42dbf..e086386b 100644 --- a/test/spec/cloud-element-templates/ElementTemplates.spec.js +++ b/test/spec/cloud-element-templates/ElementTemplates.spec.js @@ -24,8 +24,10 @@ import zeebeModdlePackage from 'zeebe-bpmn-moddle/resources/zeebe'; import diagramXML from './ElementTemplates.bpmn'; import integrationXML from './fixtures/integration.bpmn'; import messageTemplates from './ElementTemplates.message-templates.json'; +import enginesTemplates from './ElementTemplates.engines-templates.json'; import templates from './fixtures/simple'; +import falsyVersionTemplate from './fixtures/falsy-version'; import complexTemplates from './fixtures/complex'; import integrationTemplates from './fixtures/integration'; import { findExtensions, findExtension } from 'src/cloud-element-templates/Helper'; @@ -343,6 +345,206 @@ describe('provider/cloud-element-templates - ElementTemplates', function() { })); + describe(' compatibility', function() { + + beforeEach(inject(function(elementTemplates) { + elementTemplates.set(enginesTemplates); + })); + + + describe('should retrieve latest compatible', function() { + + it('single template', inject(function(elementTemplates) { + + // given + elementTemplates.setEngines({ + camunda: '8.6.3' + }); + + // when + const templates = elementTemplates.getLatest('example.engines.test.basic'); + + // then + expect(templates).to.have.length(1); + expect(templates[0].version).to.eql(3); + })); + + + it('all templates', inject(function(elementTemplates) { + + // given + elementTemplates.setEngines({ + camunda: '8.6.3' + }); + + // when + const templates = elementTemplates.getLatest(); + + // then + // expect all compatible templates to be returned + // example.engines.test.multiple v2 + // example.engines.test.basic v2 + expect(templates).to.have.length(2); + })); + + }); + + + it('should retrieve older compatible', inject(function(elementTemplates) { + + // given + elementTemplates.setEngines({ + camunda: '8.5' + }); + + // when + const templates = elementTemplates.getLatest('example.engines.test.basic'); + + // then + expect(templates).to.have.length(1); + expect(templates[0].version).to.eql(2); + })); + + + it('should retrieve fallback (no meta-data)', inject(function(elementTemplates) { + + // given + elementTemplates.setEngines({ + camunda: '4' + }); + + // when + const templates = elementTemplates.getLatest('example.engines.test.basic'); + + // then + expect(templates).to.have.length(1); + expect(templates[0].version).to.eql(1); + })); + + + describe('should handle no context provided', function() { + + it('single template', inject(function(elementTemplates) { + + // given + elementTemplates.setEngines({}); + + // when + const templates = elementTemplates.getLatest('example.engines.test.basic'); + + // then + expect(templates).to.have.length(1); + expect(templates[0].version).to.eql(3); + })); + + + it('list templates', inject(function(elementTemplates) { + + // given + elementTemplates.setEngines({}); + + // when + const templates = elementTemplates.getLatest(); + + // then + // example.engines.test.multiple v2 + // example.engines.test.basic v3 + // example.engines.test.broken v1 + expect(templates).to.have.length(3); + })); + + }); + + + it('should support multiple engines', inject(function(elementTemplates) { + + // given + elementTemplates.setEngines({ + camunda: '8.6', + webModeler: '4.3' + }); + + // when + const templates = elementTemplates.getLatest('example.engines.test.multiple'); + + // then + expect(templates).to.have.length(1); + expect(templates[0].version).to.eql(2); + })); + + + it('should exclude engine', inject(function(elementTemplates) { + + // given + elementTemplates.setEngines({ + camunda: '8.6', + webModeler: '4.3', + desktopModeler: '5.4' + }); + + // when + const templates = elementTemplates.getLatest('example.engines.test.multiple'); + + // then + expect(templates).to.have.length(1); + expect(templates[0].version).to.eql(1); + })); + + + it('should ignore incompatible', inject(function(elementTemplates) { + + // given + elementTemplates.setEngines({ + camunda: '8.4' + }); + + // when + const templates = elementTemplates.getLatest('example.engines.test.multiple'); + + // then + expect(templates).to.be.empty; + })); + + + it('should handle broken provided at run-time', inject(function(elementTemplates) { + + // given + elementTemplates.setEngines({ + camunda: 'one-hundred' + }); + + // when + const templates = elementTemplates.getLatest('example.engines.test.basic'); + + // then + // we ignore the context entry, assume it is not there + expect(templates).to.have.length(1); + expect(templates[0].version).to.eql(3); + })); + + + it('should handle broken provided by template', inject(function(elementTemplates) { + + // given + elementTemplates.setEngines({ + camunda: '8.6' + }); + + // when + const templates = elementTemplates.getLatest('example.engines.test.broken'); + + // then + expect(templates).to.be.empty; + + // and + // we still regard such template as a valid template + const template = elementTemplates.get('example.engines.test.broken', 1); + expect(template).to.exist; + })); + + }); + + it('should throw for invalid argument', inject(function(elementTemplates) { // then @@ -542,6 +744,84 @@ describe('provider/cloud-element-templates - ElementTemplates', function() { }); + describe('set', function() { + + it('should set templates', inject(function(elementTemplates) { + + // when + elementTemplates.set(templates.slice(0, 3)); + + // then + expect(elementTemplates.getAll()).to.have.length(3); + })); + + + it('should not ignore version set to 0', inject(function(elementTemplates) { + + // when + elementTemplates.set(falsyVersionTemplate); + + // then + expect(elementTemplates.get(falsyVersionTemplate[0].id, 0)).to.exist; + })); + + + it('should emit event', inject(function(elementTemplates, eventBus) { + + // given + const spy = sinon.spy(); + + eventBus.on('elementTemplates.changed', spy); + + // when + elementTemplates.set(templates); + + // then + expect(spy).to.have.been.calledOnce; + })); + + }); + + + describe('getEngines', function() { + + it('should provide set engines', inject(function(elementTemplates) { + + // when + elementTemplates.setEngines({ + 'camunda': '8', + 'other': '100.5' + }); + + // then + expect(elementTemplates.getEngines()).to.eql({ + 'camunda': '8.0.0', + 'other': '100.5.0' + }); + })); + + }); + + + describe('setEngines', function() { + + it('should emit event', inject(function(elementTemplates, eventBus) { + + // given + const spy = sinon.spy(); + + eventBus.on('elementTemplates.engines.changed', spy); + + // when + elementTemplates.setEngines({}); + + // then + expect(spy).to.have.been.calledOnce; + })); + + }); + + describe('applyTemplate', function() { beforeEach(inject(function(elementTemplates) { @@ -866,6 +1146,7 @@ describe('provider/cloud-element-templates - ElementTemplates', function() { expect(getBusinessObject(task).get('zeebe:modelerTemplateIcon')).to.not.exist; })); + it('should fire elementTemplates.unlink event', inject(function(elementRegistry, elementTemplates, eventBus) { // given @@ -1136,6 +1417,117 @@ describe('provider/cloud-element-templates - ElementTemplates', function() { }); + + describe('isCompatible', function() { + + const compatibleTemplate = { + engines: { + camunda: '^8.5' + } + }; + + const incompatibleTemplate = { + engines: { + camunda: '^8.6' + } + }; + + + it('should accept compatible', inject(function(elementTemplates) { + + // given + elementTemplates.setEngines({ + camunda: '8.5' + }); + + // then + expect(elementTemplates.isCompatible(compatibleTemplate)).to.be.true; + })); + + + it('should reject incompatible', inject(function(elementTemplates) { + + // given + elementTemplates.setEngines({ + camunda: '8.5' + }); + + // then + expect(elementTemplates.isCompatible(incompatibleTemplate)).to.be.false; + })); + + + it('should accept non matching engine', inject(function(elementTemplates) { + + // given + elementTemplates.setEngines({ + nonMatchingEngine: '8.5' + }); + + // then + expect(elementTemplates.isCompatible(compatibleTemplate)).to.be.true; + expect(elementTemplates.isCompatible(incompatibleTemplate)).to.be.true; + })); + + }); + + + describe('error handling', function() { + + // given + const invalidEngines = { + camunda: '8.5', + invalid: 'not-a-semver' + }; + + it('should filter invalid on set', inject(function(elementTemplates) { + + // when + elementTemplates.setEngines(invalidEngines); + + // then + expect(elementTemplates.getEngines()).to.have.property('camunda'); + expect(elementTemplates.getEngines()).to.not.have.property('invalid'); + })); + }); + +}); + + +describe('provider/cloud-element-templates - ElementTemplates - error handling on instantiation', function() { + + let container; + + beforeEach(function() { + container = TestContainer.get(this); + }); + + // given + const invalidEngines = { + camunda: '8.5', + invalid: 'not-a-semver' + }; + + beforeEach(bootstrapModeler(diagramXML, { + container: container, + modules: [ + coreModule, + modelingModule, + elementTemplatesCoreModule, + ], + elementTemplates: { + engines: invalidEngines + } + })); + + + it('should filter invalid on instantiation', inject(function(elementTemplates) { + + // then + expect(elementTemplates.getEngines()).to.have.property('camunda', '8.5.0'); + expect(elementTemplates.getEngines()).to.not.have.property('invalid'); + })); + }); diff --git a/test/spec/cloud-element-templates/Validator.spec.js b/test/spec/cloud-element-templates/Validator.spec.js index c63f4be6..1b2a8e8e 100644 --- a/test/spec/cloud-element-templates/Validator.spec.js +++ b/test/spec/cloud-element-templates/Validator.spec.js @@ -24,6 +24,7 @@ describe('provider/cloud-element-templates - Validator', function() { moddle = new BPMNModdle(); }); + describe('schema version', function() { it('should accept when template and library have the same version', function() { @@ -584,4 +585,42 @@ describe('provider/cloud-element-templates - Validator', function() { }); + + describe('engines validation', function() { + + it('should accept template with valid semver range', function() { + + // given + const templates = new Validator(moddle); + + const templateDescriptor = require('./fixtures/engines'); + + // when + templates.addAll(templateDescriptor); + + // then + expect(errors(templates)).to.be.empty; + + expect(valid(templates)).to.have.length(templateDescriptor.length); + }); + + + it('should reject template with invalid semver range', function() { + + // given + const templates = new Validator(moddle); + + const templateDescriptor = require('./fixtures/engines-invalid'); + + // when + templates.addAll(templateDescriptor); + + // then + expect(errors(templates)).to.contain('Engine specifies invalid semver range '); + + expect(valid(templates)).to.be.empty; + }); + + }); + }); diff --git a/test/spec/cloud-element-templates/fixtures/engines-invalid.json b/test/spec/cloud-element-templates/fixtures/engines-invalid.json new file mode 100644 index 00000000..540001e3 --- /dev/null +++ b/test/spec/cloud-element-templates/fixtures/engines-invalid.json @@ -0,0 +1,16 @@ +[ + { + "$schema": "https://unpkg.com/@camunda/zeebe-element-templates-json-schema/resources/schema.json", + "id": "example.engines.test.broken", + "name": " Test - broken Semver range", + "description": "specifies broken semver range", + "version": 1, + "engines": { + "camunda": "invalid-version" + }, + "appliesTo": [ + "bpmn:Task" + ], + "properties": [] + } +] \ No newline at end of file diff --git a/test/spec/cloud-element-templates/fixtures/engines.json b/test/spec/cloud-element-templates/fixtures/engines.json new file mode 100644 index 00000000..c525f144 --- /dev/null +++ b/test/spec/cloud-element-templates/fixtures/engines.json @@ -0,0 +1,97 @@ +[ + { + "$schema": "https://unpkg.com/@camunda/zeebe-element-templates-json-schema/resources/schema.json", + "id": "example.engines.test.multiple", + "name": " Test - Multiple", + "description": "does not match if { desktopModeler: >=1 } is provided", + "version": 2, + "engines": { + "camunda": "^8.6", + "webModeler": "^4.1", + "desktopModeler": "^0" + }, + "appliesTo": [ + "bpmn:Task" + ], + "properties": [] + }, + { + "$schema": "https://unpkg.com/@camunda/zeebe-element-templates-json-schema/resources/schema.json", + "id": "example.engines.test.multiple", + "name": " Test - Multiple", + "description": "matches if { camunda: ^8.6, webModeler: ^4.1 } engine is indicated, or properties are not provided", + "version": 1, + "engines": { + "camunda": "^8.6", + "webModeler": "^4.1" + }, + "appliesTo": [ + "bpmn:Task" + ], + "properties": [] + }, + + { + "$schema": "https://unpkg.com/@camunda/zeebe-element-templates-json-schema/resources/schema.json", + "id": "example.engines.test.basic", + "name": " Test - Basic", + "description": "matches if { camunda: ^8.6 }, or if no engine is provided", + "version": 3, + "engines": { + "camunda": "^8.6" + }, + "appliesTo": [ + "bpmn:Task" + ], + "properties": [] + }, + { + "$schema": "https://unpkg.com/@camunda/zeebe-element-templates-json-schema/resources/schema.json", + "id": "example.engines.test.basic", + "name": " Test - Basic", + "description": "matches if { camunda: ^8.5 }, or if no engine is provided", + "version": 2, + "engines": { + "camunda": "^8.5" + }, + "appliesTo": [ + "bpmn:Task" + ], + "properties": [] + }, + { + "$schema": "https://unpkg.com/@camunda/zeebe-element-templates-json-schema/resources/schema.json", + "id": "example.engines.test.basic", + "name": " Test - Basic", + "description": "always matches", + "version": 1, + "appliesTo": [ + "bpmn:Task" + ], + "properties": [] + }, + + { + "$schema": "https://unpkg.com/@camunda/zeebe-element-templates-json-schema/resources/schema.json", + "id": "example.engines.test.incompatible", + "name": " Test - incompatible", + "description": "incompatible with all camunda versions", + "engines": { + "camunda": "0" + }, + "appliesTo": [ + "bpmn:Task" + ], + "properties": [ + { + "label": "Task Header 2", + "type": "String", + "value": "header-2-value", + "binding": { + "type": "zeebe:taskHeader", + "key": "header-2-key" + } + } + ] + } +] \ No newline at end of file diff --git a/test/spec/cloud-element-templates/fixtures/falsy-version.json b/test/spec/cloud-element-templates/fixtures/falsy-version.json new file mode 100644 index 00000000..86d6fd75 --- /dev/null +++ b/test/spec/cloud-element-templates/fixtures/falsy-version.json @@ -0,0 +1,10 @@ +[ + { + "$schema": "https://unpkg.com/@camunda/zeebe-element-templates-json-schema/resources/schema.json", + "id": "foo", + "name":"Foo 1", + "version": 0, + "appliesTo": [ "bpmn:Task" ], + "properties": [] + } +] \ No newline at end of file diff --git a/test/spec/element-templates/ElementTemplates.engines-templates.json b/test/spec/element-templates/ElementTemplates.engines-templates.json new file mode 100644 index 00000000..1f4d7fef --- /dev/null +++ b/test/spec/element-templates/ElementTemplates.engines-templates.json @@ -0,0 +1,82 @@ +[ + { + "id": "example.engines.test.multiple", + "name": " Test - Multiple", + "description": "does not match if { desktopModeler: >=1 } is provided", + "version": 2, + "engines": { + "camunda": "^7.14", + "webModeler": "^4.1", + "desktopModeler": "^0" + }, + "appliesTo": [ + "bpmn:Task" + ], + "properties": [] + }, + { + "id": "example.engines.test.multiple", + "name": " Test - Multiple", + "description": "matches if { camunda: ^7.14, webModeler: ^4.1 } engine is indicated, or properties are not provided", + "version": 1, + "engines": { + "camunda": "^7.14", + "webModeler": "^4.1" + }, + "appliesTo": [ + "bpmn:Task" + ], + "properties": [] + }, + + { + "id": "example.engines.test.basic", + "name": " Test - Basic", + "description": "matches if { camunda: ^7.14 }, or if no engine is provided", + "version": 3, + "engines": { + "camunda": "^7.14" + }, + "appliesTo": [ + "bpmn:Task" + ], + "properties": [] + }, + { + "id": "example.engines.test.basic", + "name": " Test - Basic", + "description": "matches if { camunda: ^7.13 }, or if no engine is provided", + "version": 2, + "engines": { + "camunda": "^7.13" + }, + "appliesTo": [ + "bpmn:Task" + ], + "properties": [] + }, + { + "id": "example.engines.test.basic", + "name": " Test - Basic", + "description": "always matches", + "version": 1, + "appliesTo": [ + "bpmn:Task" + ], + "properties": [] + }, + + { + "id": "example.engines.test.broken", + "name": " Test - broken Semver range", + "description": "specifies broken semver range", + "version": 1, + "engines": { + "camunda": "invalid-version" + }, + "appliesTo": [ + "bpmn:Task" + ], + "properties": [] + } +] \ No newline at end of file diff --git a/test/spec/element-templates/ElementTemplates.spec.js b/test/spec/element-templates/ElementTemplates.spec.js index cd105620..b57ccc2d 100644 --- a/test/spec/element-templates/ElementTemplates.spec.js +++ b/test/spec/element-templates/ElementTemplates.spec.js @@ -20,6 +20,7 @@ import diagramXML from './ElementTemplates.bpmn'; import templates from './fixtures/simple'; import falsyVersionTemplate from './fixtures/falsy-version'; +import enginesTemplates from './ElementTemplates.engines-templates.json'; describe('provider/element-templates - ElementTemplates', function() { @@ -219,6 +220,206 @@ describe('provider/element-templates - ElementTemplates', function() { })); + describe(' compatibility', function() { + + beforeEach(inject(function(elementTemplates) { + elementTemplates.set(enginesTemplates); + })); + + + describe('should retrieve latest compatible', function() { + + it('single template', inject(function(elementTemplates) { + + // given + elementTemplates.setEngines({ + camunda: '7.14.3' + }); + + // when + const templates = elementTemplates.getLatest('example.engines.test.basic'); + + // then + expect(templates).to.have.length(1); + expect(templates[0].version).to.eql(3); + })); + + + it('all templates', inject(function(elementTemplates) { + + // given + elementTemplates.setEngines({ + camunda: '7.14.3' + }); + + // when + const templates = elementTemplates.getLatest(); + + // then + // expect all compatible templates to be returned + // example.engines.test.multiple v2 + // example.engines.test.basic v2 + expect(templates).to.have.length(2); + })); + + }); + + + it('should retrieve older compatible', inject(function(elementTemplates) { + + // given + elementTemplates.setEngines({ + camunda: '7.13' + }); + + // when + const templates = elementTemplates.getLatest('example.engines.test.basic'); + + // then + expect(templates).to.have.length(1); + expect(templates[0].version).to.eql(2); + })); + + + it('should retrieve fallback (no meta-data)', inject(function(elementTemplates) { + + // given + elementTemplates.setEngines({ + camunda: '4' + }); + + // when + const templates = elementTemplates.getLatest('example.engines.test.basic'); + + // then + expect(templates).to.have.length(1); + expect(templates[0].version).to.eql(1); + })); + + + describe('should handle no context provided', function() { + + it('single template', inject(function(elementTemplates) { + + // given + elementTemplates.setEngines({}); + + // when + const templates = elementTemplates.getLatest('example.engines.test.basic'); + + // then + expect(templates).to.have.length(1); + expect(templates[0].version).to.eql(3); + })); + + + it('list templates', inject(function(elementTemplates) { + + // given + elementTemplates.setEngines({}); + + // when + const templates = elementTemplates.getLatest(); + + // then + // example.engines.test.multiple v2 + // example.engines.test.basic v3 + // example.engines.test.broken v1 + expect(templates).to.have.length(3); + })); + + }); + + + it('should support multiple engines', inject(function(elementTemplates) { + + // given + elementTemplates.setEngines({ + camunda: '7.14', + webModeler: '4.3' + }); + + // when + const templates = elementTemplates.getLatest('example.engines.test.multiple'); + + // then + expect(templates).to.have.length(1); + expect(templates[0].version).to.eql(2); + })); + + + it('should exclude engine', inject(function(elementTemplates) { + + // given + elementTemplates.setEngines({ + camunda: '7.14', + webModeler: '4.3', + desktopModeler: '5.4' + }); + + // when + const templates = elementTemplates.getLatest('example.engines.test.multiple'); + + // then + expect(templates).to.have.length(1); + expect(templates[0].version).to.eql(1); + })); + + + it('should ignore incompatible', inject(function(elementTemplates) { + + // given + elementTemplates.setEngines({ + camunda: '7.12' + }); + + // when + const templates = elementTemplates.getLatest('example.engines.test.multiple'); + + // then + expect(templates).to.be.empty; + })); + + + it('should handle broken provided at run-time', inject(function(elementTemplates) { + + // given + elementTemplates.setEngines({ + camunda: 'one-hundred' + }); + + // when + const templates = elementTemplates.getLatest('example.engines.test.basic'); + + // then + // we ignore the context entry, assume it is not there + expect(templates).to.have.length(1); + expect(templates[0].version).to.eql(3); + })); + + + it('should handle broken provided by template', inject(function(elementTemplates) { + + // given + elementTemplates.setEngines({ + camunda: '7.14' + }); + + // when + const templates = elementTemplates.getLatest('example.engines.test.broken'); + + // then + expect(templates).to.be.empty; + + // and + // we still regard such template as a valid template + const template = elementTemplates.get('example.engines.test.broken', 1); + expect(template).to.exist; + })); + + }); + + it('should throw for invalid argument', inject(function(elementTemplates) { // then @@ -378,6 +579,40 @@ describe('provider/element-templates - ElementTemplates', function() { expect(elementTemplates.get(falsyVersionTemplate[0].id, 0)).to.exist; })); + + it('should emit event', inject(function(elementTemplates, eventBus) { + + // given + const spy = sinon.spy(); + + eventBus.on('elementTemplates.changed', spy); + + // when + elementTemplates.set(templates); + + // then + expect(spy).to.have.been.calledOnce; + })); + + }); + + + describe('setEngines', function() { + + it('should emit event', inject(function(elementTemplates, eventBus) { + + // given + const spy = sinon.spy(); + + eventBus.on('elementTemplates.engines.changed', spy); + + // when + elementTemplates.setEngines({}); + + // then + expect(spy).to.have.been.calledOnce; + })); + }); @@ -578,6 +813,80 @@ describe('provider/element-templates - ElementTemplates', function() { }); + + describe('isCompatible', function() { + + const compatibleTemplate = { + engines: { + camunda: '^8.5' + } + }; + + const incompatibleTemplate = { + engines: { + camunda: '^8.6' + } + }; + + + it('should accept compatible', inject(function(elementTemplates) { + + // given + elementTemplates.setEngines({ + camunda: '8.5' + }); + + // then + expect(elementTemplates.isCompatible(compatibleTemplate)).to.be.true; + })); + + + it('should reject incompatible', inject(function(elementTemplates) { + + // given + elementTemplates.setEngines({ + camunda: '8.5' + }); + + // then + expect(elementTemplates.isCompatible(incompatibleTemplate)).to.be.false; + })); + + + it('should accept non matching engine', inject(function(elementTemplates) { + + // given + elementTemplates.setEngines({ + nonMatchingEngine: '8.5' + }); + + // then + expect(elementTemplates.isCompatible(compatibleTemplate)).to.be.true; + expect(elementTemplates.isCompatible(incompatibleTemplate)).to.be.true; + })); + + }); + + + describe('error handling', function() { + + // given + const invalidEngines = { + camunda: '7.12', + invalid: 'not-a-semver' + }; + + it('should filter invalid on set', inject(function(elementTemplates) { + + // when + elementTemplates.setEngines(invalidEngines); + + // then + expect(elementTemplates.getEngines()).to.have.property('camunda'); + expect(elementTemplates.getEngines()).to.not.have.property('invalid'); + })); + }); + }); diff --git a/test/spec/element-templates/ElementTemplatesLoader.spec.js b/test/spec/element-templates/ElementTemplatesLoader.spec.js index 366fc0b3..6c8558db 100644 --- a/test/spec/element-templates/ElementTemplatesLoader.spec.js +++ b/test/spec/element-templates/ElementTemplatesLoader.spec.js @@ -34,6 +34,58 @@ describe('provider/element-templates - ElementTemplatesLoader', function() { }); + describe('init with config={ loadTemplates } as Array', function() { + + beforeEach(bootstrapModeler(diagramXML, { + container: container, + modules, + moddleExtensions: { + camunda: camundaModdlePackage + }, + elementTemplates: { + loadTemplates: templateDescriptors + } + })); + + it('should configure elementTemplates service', inject(function(elementTemplates) { + + // then + expect(elementTemplates.getAll()).to.eql(templateDescriptors); + })); + + }); + + + describe('init with config={ loadTemplates } as function', function() { + + let provider = function(done) { + done(null, templateDescriptors); + }; + + const templateProviderFn = function(done) { + provider(done); + }; + + beforeEach(bootstrapModeler(diagramXML, { + container: container, + modules, + moddleExtensions: { + camunda: camundaModdlePackage + }, + elementTemplates: { + loadTemplates: templateProviderFn + } + })); + + it('should configure elementTemplates service', inject(function(elementTemplates) { + + // then + expect(elementTemplates.getAll()).to.eql(templateDescriptors); + })); + + }); + + describe('init with Array', function() { beforeEach(bootstrapModeler(diagramXML, { diff --git a/test/spec/element-templates/Validator.spec.js b/test/spec/element-templates/Validator.spec.js index a8a5726a..817f41fc 100644 --- a/test/spec/element-templates/Validator.spec.js +++ b/test/spec/element-templates/Validator.spec.js @@ -712,4 +712,42 @@ describe('provider/element-templates - Validator', function() { }); + + describe('engines validation', function() { + + it('should accept template with valid semver range', function() { + + // given + const templates = new Validator(moddle); + + const templateDescriptor = require('./fixtures/engines'); + + // when + templates.addAll(templateDescriptor); + + // then + expect(errors(templates)).to.be.empty; + + expect(valid(templates)).to.have.length(templateDescriptor.length); + }); + + + it('should reject template with invalid semver range', function() { + + // given + const templates = new Validator(moddle); + + const templateDescriptor = require('./fixtures/engines-invalid'); + + // when + templates.addAll(templateDescriptor); + + // then + expect(errors(templates)).to.contain('Engine specifies invalid semver range '); + + expect(valid(templates)).to.be.empty; + }); + + }); + }); diff --git a/test/spec/element-templates/fixtures/engines-invalid.json b/test/spec/element-templates/fixtures/engines-invalid.json new file mode 100644 index 00000000..a01cce2f --- /dev/null +++ b/test/spec/element-templates/fixtures/engines-invalid.json @@ -0,0 +1,15 @@ +[ + { + "id": "example.engines.test.basic", + "name": " Test - Basic", + "description": "basic template with invalid engines", + "version": 3, + "engines": { + "camunda": "invalid-version" + }, + "appliesTo": [ + "bpmn:Task" + ], + "properties": [] + } +] \ No newline at end of file diff --git a/test/spec/element-templates/fixtures/engines.json b/test/spec/element-templates/fixtures/engines.json new file mode 100644 index 00000000..aec9dff8 --- /dev/null +++ b/test/spec/element-templates/fixtures/engines.json @@ -0,0 +1,91 @@ +[ + { + "id": "example.engines.test.multiple", + "name": " Test - Multiple", + "description": "does not match if { desktopModeler: >=1 } is provided", + "version": 2, + "engines": { + "camunda": "^7.13", + "webModeler": "^4.1", + "desktopModeler": "^0" + }, + "appliesTo": [ + "bpmn:Task" + ], + "properties": [] + }, + { + "id": "example.engines.test.multiple", + "name": " Test - Multiple", + "description": "matches if { camunda: ^7.13, webModeler: ^4.1 } engine is indicated, or properties are not provided", + "version": 1, + "engines": { + "camunda": "^7.13", + "webModeler": "^4.1" + }, + "appliesTo": [ + "bpmn:Task" + ], + "properties": [] + }, + + { + "id": "example.engines.test.basic", + "name": " Test - Basic", + "description": "matches if { camunda: ^7.13 }, or if no engine is provided", + "version": 3, + "engines": { + "camunda": "^7.13" + }, + "appliesTo": [ + "bpmn:Task" + ], + "properties": [] + }, + { + "id": "example.engines.test.basic", + "name": " Test - Basic", + "description": "matches if { camunda: <=7.12 }, or if no engine is provided", + "version": 2, + "engines": { + "camunda": "<=7.12" + }, + "appliesTo": [ + "bpmn:Task" + ], + "properties": [] + }, + { + "id": "example.engines.test.basic", + "name": " Test - Basic", + "description": "always matches", + "version": 1, + "appliesTo": [ + "bpmn:Task" + ], + "properties": [] + }, + + { + "id": "example.engines.test.incompatible", + "name": " Test - incompatible", + "description": "incompatible with all camunda versions", + "engines": { + "camunda": "0" + }, + "appliesTo": [ + "bpmn:Task" + ], + "properties": [ + { + "label": "Custom Property 2", + "type": "String", + "value": "property-2-value", + "binding": { + "type": "camunda:property", + "name": "property-2-key" + } + } + ] + } +] \ No newline at end of file