diff --git a/src/element-templates/ElementTemplates.js b/src/element-templates/ElementTemplates.js index 7dcf7ba..9f2106d 100644 --- a/src/element-templates/ElementTemplates.js +++ b/src/element-templates/ElementTemplates.js @@ -129,26 +129,21 @@ export default class ElementTemplates { 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 latest = this._templatesById[ id ].latest; - const isCompat = this.isCompatible(template); - if (!isCompat) { - return; - } - - if (isUndefined(latest.version) || latest.version < version || !this.isCompatible(latest)) { - this._templatesById[ id ].latest = template; + if (this.isCompatible(template)) { + if (!latest || isUndefined(latest.version) || latest.version < version) { + this._templatesById[ id ].latest = template; + } } }); @@ -230,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) ; }; diff --git a/test/spec/cloud-element-templates/ElementTemplates.spec.js b/test/spec/cloud-element-templates/ElementTemplates.spec.js index 456b1ad..8465371 100644 --- a/test/spec/cloud-element-templates/ElementTemplates.spec.js +++ b/test/spec/cloud-element-templates/ElementTemplates.spec.js @@ -384,8 +384,7 @@ describe('provider/cloud-element-templates - ElementTemplates', function() { // expect all compatible templates to be returned // example.engines.test.multiple v2 // example.engines.test.basic v2 - // example.engines.test.broken v0 - expect(templates).to.have.length(3); + expect(templates).to.have.length(2); })); }); @@ -535,9 +534,12 @@ describe('provider/cloud-element-templates - ElementTemplates', function() { const templates = elementTemplates.getLatest('example.engines.test.broken'); // then - // assumption: we still regard such template as a valid template - expect(templates).to.have.length(1); - expect(templates[0].version).to.eql(1); + 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; })); }); 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 0000000..1f4d7fe --- /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 dc0473e..b57ccc2 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 @@ -612,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'); + })); + }); + });