From 70e3319f92f6b1d494a07a9f2e5681d2ee6b8f7c Mon Sep 17 00:00:00 2001 From: Nico Rehwaldt Date: Fri, 16 Dec 2022 17:52:00 +0100 Subject: [PATCH 1/6] feat: handle extension namespace clashes This resolves name conflicts by making extensions only available through their namespace names. Related to https://github.com/bpmn-io/moddle/issues/36 BREAKING CHANGES: * Extensions are not registered with their local names anymore, but only via their namespace prefixed names such as `someProperty` -> `e:someProperty` --- lib/descriptor-builder.js | 37 +++++++++++----- test/fixtures/model/extension/custom.json | 1 + test/spec/extension.js | 51 +++++++++++++++++++---- 3 files changed, 69 insertions(+), 20 deletions(-) diff --git a/lib/descriptor-builder.js b/lib/descriptor-builder.js index c76e2b0..9fbbfd2 100644 --- a/lib/descriptor-builder.js +++ b/lib/descriptor-builder.js @@ -42,19 +42,20 @@ DescriptorBuilder.prototype.build = function() { * @param {Object} p * @param {Number} [idx] * @param {Boolean} [validate=true] + * @param {Boolean} [inherited=false] */ -DescriptorBuilder.prototype.addProperty = function(p, idx, validate) { +DescriptorBuilder.prototype.addProperty = function(p, idx, validate, inherited) { if (typeof idx === 'boolean') { validate = idx; idx = undefined; } - this.addNamedProperty(p, validate !== false); + this.addNamedProperty(p, validate !== false, inherited); var properties = this.properties; - if (idx !== undefined) { + if (typeof idx === 'number') { properties.splice(idx, 0, p); } else { properties.push(p); @@ -105,10 +106,16 @@ DescriptorBuilder.prototype.replaceProperty = function(oldProperty, newProperty, // * validate only if this is a "rename" operation // * add at specific index unless we "replace" // - this.addProperty(newProperty, replace ? undefined : idx, rename); + this.addProperty(newProperty, replace ? undefined : idx, rename, oldProperty.inherited); + + if (oldProperty.inherited) { + + // make new property available under old localName + propertiesByName[oldNameNs.localName] = newProperty; + } // make new property available under old name - propertiesByName[oldNameNs.name] = propertiesByName[oldNameNs.localName] = newProperty; + propertiesByName[oldNameNs.name] = newProperty; }; @@ -130,16 +137,23 @@ DescriptorBuilder.prototype.redefineProperty = function(p, targetPropertyName, r delete p.redefines; }; -DescriptorBuilder.prototype.addNamedProperty = function(p, validate) { +DescriptorBuilder.prototype.addNamedProperty = function(p, validate, inherited) { var ns = p.ns, propsByName = this.propertiesByName; if (validate) { + if (inherited) { + this.assertNotDefined(p, ns.localName); + } + this.assertNotDefined(p, ns.name); - this.assertNotDefined(p, ns.localName); } - propsByName[ns.name] = propsByName[ns.localName] = p; + if (inherited) { + propsByName[ns.localName] = p; + } + + propsByName[ns.name] = p; }; DescriptorBuilder.prototype.removeNamedProperty = function(p) { @@ -218,8 +232,8 @@ DescriptorBuilder.prototype.addTrait = function(t, inherited) { // clone property to allow extensions p = assign({}, p, { - name: p.ns.localName, - inherited: inherited + name: inherited ? p.ns.localName : p.ns.name, + inherited }); Object.defineProperty(p, 'definedBy', { @@ -239,7 +253,8 @@ DescriptorBuilder.prototype.addTrait = function(t, inherited) { if (p.isId) { this.setIdProperty(p); } - this.addProperty(p); + + this.addProperty(p, null, true, inherited); } }, this)); diff --git a/test/fixtures/model/extension/custom.json b/test/fixtures/model/extension/custom.json index 225b945..273adad 100644 --- a/test/fixtures/model/extension/custom.json +++ b/test/fixtures/model/extension/custom.json @@ -8,6 +8,7 @@ "superClass": [ "Base" ], "extends": [ "b:Root" ], "properties": [ + { "name": "own", "type": "b:Own" }, { "name": "customAttr", "type": "Integer", "isAttr": true }, { "name": "generic", "type": "CustomGeneric", "redefines": "b:Root#generic" } ] diff --git a/test/spec/extension.js b/test/spec/extension.js index 84c522b..7fa9ea6 100644 --- a/test/spec/extension.js +++ b/test/spec/extension.js @@ -80,15 +80,42 @@ describe('extension', function() { var ComplexType = model.getType('b:Root'); // when - var descriptor = model.getElementDescriptor(ComplexType), - customAttrDescriptor = descriptor.propertiesByName['customAttr'], - customBaseAttrDescriptor = descriptor.propertiesByName['customBaseAttr'], - ownAttrDescriptor = descriptor.propertiesByName['ownAttr']; + var descriptor = model.getElementDescriptor(ComplexType); + var propertiesByName = descriptor.propertiesByName; // then - expect(customAttrDescriptor.inherited).to.be.false; - expect(customBaseAttrDescriptor.inherited).to.be.false; - expect(ownAttrDescriptor.inherited).to.be.true; + expect(propertiesByName).to.include.keys([ + 'c:customAttr', + 'c:customBaseAttr', + 'ownAttr' + ]); + + expect(propertiesByName).not.to.include.keys([ + 'customAttr', + 'customBaseAttr' + ]); + + // then + expect(propertiesByName['c:customAttr']).to.have.property('inherited', false); + expect(propertiesByName['c:customBaseAttr']).to.have.property('inherited', false); + expect(propertiesByName['ownAttr']).to.have.property('inherited', true); + }); + + + it('should handle conflicting names', function() { + + // given + var ComplexType = model.getType('b:Root'); + + // when + var descriptor = model.getElementDescriptor(ComplexType); + var propertiesByName = descriptor.propertiesByName; + + // then + expect(propertiesByName).to.include.keys([ + 'own', + 'c:own' + ]); }); }); @@ -111,7 +138,13 @@ describe('extension', function() { }); // then - expect(root.customAttr).to.eql(-1); + expect(root.customAttr).not.to.exist; + + expect(root['c:customAttr']).not.to.exist; + expect(root.get('c:customAttr')).not.to.exist; + + // but saved as extension attribute + expect(root.get('customAttr')).to.eql(-1); }); @@ -138,7 +171,7 @@ describe('extension', function() { }); // then - expect(root.generic).to.eql(customGeneric); + expect(root['c:generic']).to.eql(customGeneric); }); }); From dd0c92db6228e9cab2bc72e3e200766df8e75bdb Mon Sep 17 00:00:00 2001 From: Nico Rehwaldt Date: Fri, 16 Dec 2022 17:58:51 +0100 Subject: [PATCH 2/6] test: rework extension test coverage --- test/fixtures/model/extension/base.json | 1 + test/fixtures/model/extension/custom.json | 3 +- test/fixtures/model/redefines/custom.json | 17 + test/fixtures/model/redefines/extension.json | 17 + test/fixtures/model/replaces/custom.json | 17 + test/fixtures/model/replaces/extension.json | 17 + test/spec/extension.js | 829 ++++++++++++++++--- 7 files changed, 787 insertions(+), 114 deletions(-) create mode 100644 test/fixtures/model/redefines/custom.json create mode 100644 test/fixtures/model/redefines/extension.json create mode 100644 test/fixtures/model/replaces/custom.json create mode 100644 test/fixtures/model/replaces/extension.json diff --git a/test/fixtures/model/extension/base.json b/test/fixtures/model/extension/base.json index 20ef6b8..357a1f3 100644 --- a/test/fixtures/model/extension/base.json +++ b/test/fixtures/model/extension/base.json @@ -6,6 +6,7 @@ { "name": "Root", "properties": [ + { "name": "id", "type": "String" }, { "name": "own", "type": "Own" }, { "name": "ownAttr", "type": "String", "isAttr": true }, { "name": "generic", "type": "Element" }, diff --git a/test/fixtures/model/extension/custom.json b/test/fixtures/model/extension/custom.json index 273adad..a0ee8d1 100644 --- a/test/fixtures/model/extension/custom.json +++ b/test/fixtures/model/extension/custom.json @@ -10,7 +10,8 @@ "properties": [ { "name": "own", "type": "b:Own" }, { "name": "customAttr", "type": "Integer", "isAttr": true }, - { "name": "generic", "type": "CustomGeneric", "redefines": "b:Root#generic" } + { "name": "generic", "type": "CustomGeneric", "redefines": "b:Root#generic" }, + { "name": "id", "type": "String", "replaces": "b:Root#id", "isAttr": true, "isId": true } ] }, { diff --git a/test/fixtures/model/redefines/custom.json b/test/fixtures/model/redefines/custom.json new file mode 100644 index 0000000..b6faaea --- /dev/null +++ b/test/fixtures/model/redefines/custom.json @@ -0,0 +1,17 @@ +{ + "name": "Redefines Custom", + "uri": "http://redefines-custom", + "prefix": "c", + "types": [ + { + "name": "Base", + "extends": [ + "b:Base" + ], + "properties": [ + { "name": "value", "type": "String" }, + { "name": "id", "type": "Integer", "redefines": "b:Base#id", "isId": true } + ] + } + ] +} \ No newline at end of file diff --git a/test/fixtures/model/redefines/extension.json b/test/fixtures/model/redefines/extension.json new file mode 100644 index 0000000..43a2931 --- /dev/null +++ b/test/fixtures/model/redefines/extension.json @@ -0,0 +1,17 @@ +{ + "name": "Redefines Extension", + "uri": "http://redefines-extension", + "prefix": "e", + "types": [ + { + "name": "Base", + "superClass": [ + "b:Base" + ], + "properties": [ + { "name": "value", "type": "String" }, + { "name": "id", "type": "Integer", "redefines": "b:Base#id", "isId": true } + ] + } + ] +} \ No newline at end of file diff --git a/test/fixtures/model/replaces/custom.json b/test/fixtures/model/replaces/custom.json new file mode 100644 index 0000000..b214081 --- /dev/null +++ b/test/fixtures/model/replaces/custom.json @@ -0,0 +1,17 @@ +{ + "name": "Replaces Extension", + "uri": "http://replace-extension", + "prefix": "c", + "types": [ + { + "name": "Base", + "extends": [ + "b:Base" + ], + "properties": [ + { "name": "value", "type": "String" }, + { "name": "id", "type": "Integer", "replaces": "b:Base#id", "isId": true } + ] + } + ] +} \ No newline at end of file diff --git a/test/fixtures/model/replaces/extension.json b/test/fixtures/model/replaces/extension.json new file mode 100644 index 0000000..846f9fc --- /dev/null +++ b/test/fixtures/model/replaces/extension.json @@ -0,0 +1,17 @@ +{ + "name": "Replaces Extension", + "uri": "http://replace-extension", + "prefix": "e", + "types": [ + { + "name": "Base", + "superClass": [ + "b:Base" + ], + "properties": [ + { "name": "value", "type": "String" }, + { "name": "id", "type": "Integer", "replaces": "b:Base#id", "isId": true } + ] + } + ] +} \ No newline at end of file diff --git a/test/spec/extension.js b/test/spec/extension.js index 7fa9ea6..82b2f8e 100644 --- a/test/spec/extension.js +++ b/test/spec/extension.js @@ -1,7 +1,9 @@ import expect from '../expect.js'; import { - createModelBuilder + createModelBuilder, + expectOrderedProperties, + getEffectiveDescriptor } from '../helper.js'; @@ -10,6 +12,22 @@ describe('extension', function() { var createModel = createModelBuilder('test/fixtures/model/'); + describe('trait', function() { + + it('should not provide meta-data', function() { + + // given + var model = createModel([ 'extension/base', 'extension/custom' ]); + + // then + expect(() => { + model.getType('c:CustomRoot'); + }).to.throw(/cannot create extending /); + }); + + }); + + describe('types', function() { describe('built-in shadowing', function() { @@ -54,47 +72,93 @@ describe('extension', function() { }); - }); + describe('extending', function() { - describe('extension', function() { + var model = createModel([ 'extension/base', 'extension/custom' ]); - var model = createModel([ 'extension/base', 'extension/custom' ]); + describe('basics', function() { - describe('trait', function() { + it('should plug-in into type hierarchy', function() { - it('should not provide meta-data', function() { + var root = model.create('b:Root'); + + // then + expect(root.$instanceOf('c:CustomRoot')).to.be.true; + }); - expect(() => { - model.getType('c:CustomRoot'); - }).to.throw(/cannot create extending /); }); - describe('descriptor', function() { + describe('properties', function() { - it('should indicate non-inherited', function() { + it('should register', function() { - // given - var ComplexType = model.getType('b:Root'); + // when + var descriptor = getEffectiveDescriptor(model, 'b:Root'); + + // then + // local properties remain + expect(descriptor.propertiesByName).to.have.property('ownAttr'); + + // extension properties are not local + expect(descriptor.propertiesByName).not.to.have.property('customAttr'); + expect(descriptor.propertiesByName).not.to.have.property('customBaseAttr'); + + // but registered as extension properties + expect(descriptor.propertiesByName).to.have.property('c:customAttr'); + expect(descriptor.propertiesByName).to.have.property('c:customBaseAttr'); + }); + + + it('should refine', function() { // when - var descriptor = model.getElementDescriptor(ComplexType); - var propertiesByName = descriptor.propertiesByName; + var descriptor = getEffectiveDescriptor(model, 'b:Root'); + + var genericProperty = descriptor.propertiesByName['c:generic']; // then + expect(genericProperty).to.exist; + expect(genericProperty.name).to.eql('c:generic'); + expect(genericProperty.type).to.eql('c:CustomGeneric'); + + // and refined original, too + expect(descriptor.propertiesByName).to.have.property('generic', genericProperty); + }); + + + it('should replace', function() { + + // when + var descriptor = getEffectiveDescriptor(model, 'b:Root'); + + var idProperty = descriptor.propertiesByName['c:id']; + + // then + expect(idProperty).to.exist; + expect(idProperty.name).to.eql('c:id'); + + // and replaced original, too + expect(descriptor.propertiesByName).to.have.property('id', idProperty); + }); + + + it('should indicate extended', function() { + + // when + var descriptor = getEffectiveDescriptor(model, 'b:Root'); + + var propertiesByName = descriptor.propertiesByName; + + // assume expect(propertiesByName).to.include.keys([ 'c:customAttr', 'c:customBaseAttr', 'ownAttr' ]); - expect(propertiesByName).not.to.include.keys([ - 'customAttr', - 'customBaseAttr' - ]); - // then expect(propertiesByName['c:customAttr']).to.have.property('inherited', false); expect(propertiesByName['c:customBaseAttr']).to.have.property('inherited', false); @@ -104,15 +168,11 @@ describe('extension', function() { it('should handle conflicting names', function() { - // given - var ComplexType = model.getType('b:Root'); - // when - var descriptor = model.getElementDescriptor(ComplexType); - var propertiesByName = descriptor.propertiesByName; + var descriptor = getEffectiveDescriptor(model, 'b:Root'); // then - expect(propertiesByName).to.include.keys([ + expect(descriptor.propertiesByName).to.include.keys([ 'own', 'c:own' ]); @@ -121,158 +181,701 @@ describe('extension', function() { }); - it('should plug-in into type hierarchy', function() { + describe('types', function() { - var root = model.create('b:Root'); + it('should provide custom', function() { + + var property = model.create('c:Property'); + + // then + expect(property.$instanceOf('c:Property')).to.be.true; + }); - // then - expect(root.$instanceOf('c:CustomRoot')).to.be.true; }); - it('should add custom attribute', function() { + describe('generics', function() { - // when - var root = model.create('b:Root', { - customAttr: -1 + it('should extend Element', function() { + + // when + var customGeneric = model.create('c:CustomGeneric', { count: 100 }); + + // then + expect(customGeneric.$instanceOf('Element')).to.be.true; }); - // then - expect(root.customAttr).not.to.exist; - expect(root['c:customAttr']).not.to.exist; - expect(root.get('c:customAttr')).not.to.exist; + it('should be part of generic collection', function() { + + var customProperty = model.create('c:Property', { key: 'foo', value: 'bar' }); + + // when + var root = model.create('b:Root', { + genericCollection: [ customProperty ] + }); + + // then + expect(root.genericCollection).to.eql([ customProperty ]); + expect(root.get('b:genericCollection')).to.eql([ customProperty ]); + }); - // but saved as extension attribute - expect(root.get('customAttr')).to.eql(-1); }); + }); + + }); - it('should refine property', function() { - // given - var Type = model.getType('b:Root'); + describe('properties', function() { - // when - var genericProperty = Type.$descriptor.propertiesByName['generic']; + describe('', function() { + + describe('same package', function() { + + var model = createModel([ 'replaces/base' ]); + + + it('should modify descriptor', function() { + + // when + var descriptor = getEffectiveDescriptor(model, 'b:Extension'); + + // then + expectOrderedProperties(descriptor, [ + 'name', + 'value', + 'id' + ]); + + const idProperty = descriptor.propertiesByName['id']; + + expect(idProperty).to.exist; + expect(descriptor.propertiesByName).to.have.property('b:id', idProperty); + }); + + + it('should instantiate', function() { + + // when + var extension = model.create('b:Extension', { + id: 10 + }); + + // then + expect(extension.id).to.eql(10); + + // unavailable via namespaced name + expect(extension['b:id']).not.to.exist; + + expect(extension.get('id')).to.eql(10); + expect(extension.get('b:id')).to.eql(10); + }); + + + it('should instantiate (ns property)', function() { + + // when + var extension = model.create('b:Extension', { + 'b:id': 10 + }); + + // then + expect(extension.id).to.eql(10); + + // unavailable via namespaced name + expect(extension['b:id']).not.to.exist; + + expect(extension.get('id')).to.eql(10); + expect(extension.get('b:id')).to.eql(10); + }); + + + it('should set', function() { + + // given + var extension = model.create('b:Extension'); + + // when + extension.set('id', 10); + + // then + expect(extension.id).to.eql(10); + + // unavailable via namespaced name + expect(extension['b:id']).not.to.exist; + + expect(extension.get('id')).to.eql(10); + expect(extension.get('b:id')).to.eql(10); + }); + + + it('should set (ns property)', function() { + + // given + var extension = model.create('b:Extension'); + + // when + extension.set('b:id', 10); + + // then + expect(extension.id).to.eql(10); + + // unavailable via namespaced name + expect(extension['b:id']).not.to.exist; + + expect(extension.get('id')).to.eql(10); + expect(extension.get('b:id')).to.eql(10); + }); - // then - expect(genericProperty.type).to.eql('c:CustomGeneric'); }); - it('should use refined property', function() { + describe('other package ', function() { - var customGeneric = model.create('c:CustomGeneric', { count: 100 }); + var model = createModel([ + 'replaces/base', + 'replaces/extension' + ]); - // when - var root = model.create('b:Root', { - generic: customGeneric + + it('should modify descriptor', function() { + + // when + var descriptor = getEffectiveDescriptor(model, 'e:Base'); + + // then + expectOrderedProperties(descriptor, [ + 'name', + 'value', + 'id' + ]); + + const idProperty = descriptor.propertiesByName['id']; + + expect(idProperty).to.exist; + expect(descriptor.propertiesByName).to.have.property('b:id', idProperty); + expect(descriptor.propertiesByName).to.have.property('e:id', idProperty); + }); + + + it('should instantiate', function() { + + // when + var extension = model.create('e:Base', { + id: 10 + }); + + // then + expect(extension.id).to.eql(10); + + // unavailable via namespaced name + expect(extension['b:id']).not.to.exist; + + expect(extension.get('id')).to.eql(10); + expect(extension.get('b:id')).to.eql(10); + expect(extension.get('e:id')).to.eql(10); + }); + + + it('should instantiate (ns property)', function() { + + // when + var extension = model.create('e:Base', { + 'e:id': 10 + }); + + // then + expect(extension.id).to.eql(10); + + // unavailable via namespaced name + expect(extension['b:id']).not.to.exist; + + expect(extension.get('id')).to.eql(10); + expect(extension.get('b:id')).to.eql(10); + expect(extension.get('e:id')).to.eql(10); + }); + + + it('should set', function() { + + // given + var extension = model.create('e:Base'); + + // when + extension.set('id', 10); + + // then + expect(extension.id).to.eql(10); + + // unavailable via namespaced name + expect(extension['b:id']).not.to.exist; + + expect(extension.get('id')).to.eql(10); + expect(extension.get('b:id')).to.eql(10); + expect(extension.get('e:id')).to.eql(10); + }); + + + it('should set (ns property)', function() { + + // given + var extension = model.create('e:Base'); + + // when + extension.set('e:id', 10); + + // then + expect(extension.id).to.eql(10); + + // unavailable via namespaced name + expect(extension['b:id']).not.to.exist; + + expect(extension.get('id')).to.eql(10); + expect(extension.get('b:id')).to.eql(10); + expect(extension.get('e:id')).to.eql(10); }); - // then - expect(root['c:generic']).to.eql(customGeneric); }); - }); + describe('other package ', function() { - describe('types', function() { + var model = createModel([ + 'replaces/base', + 'replaces/custom' + ]); - it('should provide custom types', function() { - var property = model.create('c:Property'); + it('should modify descriptor', function() { + + // when + var descriptor = getEffectiveDescriptor(model, 'b:Base'); + + // then + expectOrderedProperties(descriptor, [ + 'name', + 'c:value', + 'c:id' + ]); + + const idProperty = descriptor.propertiesByName['id']; + + expect(idProperty).to.exist; + expect(descriptor.propertiesByName).to.have.property('b:id', idProperty); + expect(descriptor.propertiesByName).to.have.property('c:id', idProperty); + }); + + + it('should instantiate', function() { + + // when + var extension = model.create('b:Base', { + id: 10 + }); + + // then + expect(extension['c:id']).to.eql(10); + + // unavailable via local name + expect(extension.id).not.to.exist; + + expect(extension.get('id')).to.eql(10); + expect(extension.get('b:id')).to.eql(10); + expect(extension.get('c:id')).to.eql(10); + }); + + + it('should instantiate (ns property)', function() { + + // when + var extension = model.create('b:Base', { + 'c:id': 10 + }); + + // then + expect(extension['c:id']).to.eql(10); + + // unavailable via local name + expect(extension.id).not.to.exist; + + expect(extension.get('id')).to.eql(10); + expect(extension.get('b:id')).to.eql(10); + expect(extension.get('c:id')).to.eql(10); + }); + + + it('should set', function() { + + // given + var extension = model.create('b:Base'); + + // when + extension.set('id', 10); + + // then + expect(extension['c:id']).to.eql(10); + + // unavailable via local name + expect(extension.id).not.to.exist; + + expect(extension.get('id')).to.eql(10); + expect(extension.get('b:id')).to.eql(10); + expect(extension.get('c:id')).to.eql(10); + }); + + + it('should set (ns property)', function() { + + // given + var extension = model.create('b:Base'); + + // when + extension.set('id', 10); + + // then + expect(extension['c:id']).to.eql(10); + + // unavailable via local name + expect(extension.id).not.to.exist; + + expect(extension.get('id')).to.eql(10); + expect(extension.get('b:id')).to.eql(10); + expect(extension.get('c:id')).to.eql(10); + }); - // then - expect(property.$instanceOf('c:Property')).to.be.true; }); }); - describe('generic', function() { + describe('', function() { - it('should extend Element', function() { + describe('same package', function() { - // when - var customGeneric = model.create('c:CustomGeneric', { count: 100 }); + var model = createModel([ 'redefines/base' ]); - // then - expect(customGeneric.$instanceOf('Element')).to.be.true; - }); + it('should modify descriptor', function() { - it('should be part of generic collection', function() { + // when + var descriptor = getEffectiveDescriptor(model, 'b:Extension'); + + // then + expectOrderedProperties(descriptor, [ + 'id', + 'name', + 'value' + ]); - var customProperty = model.create('c:Property', { key: 'foo', value: 'bar' }); + const idProperty = descriptor.propertiesByName['id']; - // when - var root = model.create('b:Root', { - genericCollection: [ customProperty ] + expect(idProperty).to.exist; + expect(descriptor.propertiesByName).to.have.property('b:id', idProperty); + }); + + + it('should instantiate', function() { + + // when + var extension = model.create('b:Extension', { + id: 10 + }); + + // then + expect(extension.id).to.eql(10); + + // unavailable via namespaced name + expect(extension['b:id']).not.to.exist; + + expect(extension.get('id')).to.eql(10); + expect(extension.get('b:id')).to.eql(10); + }); + + + it('should instantiate (ns property)', function() { + + // when + var extension = model.create('b:Extension', { + 'b:id': 10 + }); + + // then + expect(extension.id).to.eql(10); + + // unavailable via namespaced name + expect(extension['b:id']).not.to.exist; + + expect(extension.get('id')).to.eql(10); + expect(extension.get('b:id')).to.eql(10); + }); + + + it('should set', function() { + + // given + var extension = model.create('b:Extension'); + + // when + extension.set('id', 10); + + // then + expect(extension.id).to.eql(10); + + // unavailable via namespaced name + expect(extension['b:id']).not.to.exist; + + expect(extension.get('id')).to.eql(10); + expect(extension.get('b:id')).to.eql(10); + }); + + + it('should set (ns property)', function() { + + // given + var extension = model.create('b:Extension'); + + // when + extension.set('b:id', 10); + + // then + expect(extension.id).to.eql(10); + + // unavailable via namespaced name + expect(extension['b:id']).not.to.exist; + + expect(extension.get('id')).to.eql(10); + expect(extension.get('b:id')).to.eql(10); }); - // then - expect(root.genericCollection).to.eql([ customProperty ]); }); - }); - }); + describe('other package ', function() { + var model = createModel([ + 'redefines/base', + 'redefines/extension' + ]); - describe('property replacement', function() { - var model = createModel([ 'replaces/base' ]); + it('should modify descriptor', function() { - it('should replace in descriptor', function() { + // when + var descriptor = getEffectiveDescriptor(model, 'e:Base'); - // given - var Extension = model.getType('b:Extension'); + // then + expectOrderedProperties(descriptor, [ + 'id', + 'name', + 'value' + ]); - // when - var descriptor = model.getElementDescriptor(Extension), - propertyNames = descriptor.properties.map(function(p) { - return p.name; + const idProperty = descriptor.propertiesByName['id']; + + expect(idProperty).to.exist; + expect(descriptor.propertiesByName).to.have.property('b:id', idProperty); + expect(descriptor.propertiesByName).to.have.property('e:id', idProperty); + }); + + + it('should instantiate', function() { + + // when + var extension = model.create('e:Base', { + id: 10 }); - // then - expect(propertyNames).to.eql([ - 'name', - 'value', - 'id' - ]); - - expect(descriptor.propertiesByName['b:id'].type).to.eql('Integer'); - expect(descriptor.propertiesByName['id'].type).to.eql('Integer'); - }); + // then + expect(extension.id).to.eql(10); - }); + // unavailable via namespaced names + expect(extension['e:id']).not.to.exist; + expect(extension['b:id']).not.to.exist; + expect(extension.get('id')).to.eql(10); + expect(extension.get('b:id')).to.eql(10); + expect(extension.get('e:id')).to.eql(10); + }); - describe('property redefinition', function() { - var model = createModel([ 'redefines/base' ]); + it('should instantiate (ns property)', function() { - it('should redefine in descriptor', function() { + // when + var extension = model.create('e:Base', { + 'e:id': 10 + }); - // given - var Extension = model.getType('b:Extension'); + // then + expect(extension.id).to.eql(10); - // when - var descriptor = model.getElementDescriptor(Extension), - propertyNames = descriptor.properties.map(function(p) { + // unavailable via namespaced names + expect(extension['e:id']).not.to.exist; + expect(extension['b:id']).not.to.exist; + + expect(extension.get('id')).to.eql(10); + expect(extension.get('b:id')).to.eql(10); + expect(extension.get('e:id')).to.eql(10); + }); + + + it('should set', function() { + + // given + var extension = model.create('e:Base'); + + // when + extension.set('id', 10); + + // then + expect(extension.id).to.eql(10); + + // unavailable via namespaced names + expect(extension['e:id']).not.to.exist; + expect(extension['b:id']).not.to.exist; + + expect(extension.get('id')).to.eql(10); + expect(extension.get('b:id')).to.eql(10); + expect(extension.get('e:id')).to.eql(10); + }); + + + it('should set (ns property)', function() { + + // given + var extension = model.create('e:Base'); + + // when + extension.set('e:id', 10); + + // then + expect(extension.id).to.eql(10); + + // unavailable via namespaced names + expect(extension['e:id']).not.to.exist; + expect(extension['b:id']).not.to.exist; + + expect(extension.get('id')).to.eql(10); + expect(extension.get('b:id')).to.eql(10); + expect(extension.get('e:id')).to.eql(10); + }); + + }); + + + describe('other package ', function() { + + var model = createModel([ + 'redefines/base', + 'redefines/custom' + ]); + + + it('should modify descriptor', function() { + + // given + var Base = model.getType('b:Base'); + + // when + var descriptor = model.getElementDescriptor(Base); + var propertyNames = descriptor.properties.map(function(p) { return p.name; }); - // then - expect(propertyNames).to.eql([ - 'id', - 'name', - 'value' - ]); - - expect(descriptor.propertiesByName['b:id'].type).to.eql('Integer'); - expect(descriptor.propertiesByName['id'].type).to.eql('Integer'); + // then + expect(propertyNames).to.eql([ + 'c:id', + 'name', + 'c:value' + ]); + + const idProperty = descriptor.propertiesByName['id']; + + expect(idProperty).to.exist; + expect(descriptor.propertiesByName).to.have.property('b:id', idProperty); + expect(descriptor.propertiesByName).to.have.property('c:id', idProperty); + }); + + + it('should instantiate', function() { + + // when + var extension = model.create('b:Base', { + id: 10 + }); + + // then + expect(extension['c:id']).to.eql(10); + + // unavailable via local name + expect(extension['id']).not.to.exist; + + expect(extension.get('id')).to.eql(10); + expect(extension.get('b:id')).to.eql(10); + expect(extension.get('c:id')).to.eql(10); + }); + + + it('should instantiate (ns property)', function() { + + // when + var extension = model.create('b:Base', { + 'c:id': 10 + }); + + // then + expect(extension['c:id']).to.eql(10); + + // unavailable via local name + expect(extension['id']).not.to.exist; + + expect(extension.get('id')).to.eql(10); + expect(extension.get('b:id')).to.eql(10); + expect(extension.get('c:id')).to.eql(10); + }); + + + it('should set', function() { + + // given + var extension = model.create('b:Base'); + + // when + extension.set('id', 10); + + // then + expect(extension['c:id']).to.eql(10); + + // unavailable via local name + expect(extension['id']).not.to.exist; + + expect(extension.get('id')).to.eql(10); + expect(extension.get('b:id')).to.eql(10); + expect(extension.get('c:id')).to.eql(10); + }); + + + it('should set (ns property)', function() { + + // given + var extension = model.create('b:Base'); + + // when + extension.set('c:id', 10); + + // then + expect(extension['c:id']).to.eql(10); + + // unavailable via local name + expect(extension['id']).not.to.exist; + + expect(extension.get('id')).to.eql(10); + expect(extension.get('b:id')).to.eql(10); + expect(extension.get('c:id')).to.eql(10); + }); + + }); + }); }); From 4d65a814479b6bb03a484cc1b95b66733a9f527e Mon Sep 17 00:00:00 2001 From: Nico Rehwaldt Date: Sat, 17 Dec 2022 15:23:18 +0100 Subject: [PATCH 3/6] test: verify multiple inheritance conflict resolution Outlines solution to #36 --- .../model/multiple-inheritance/base.json | 17 +++++++++++ .../model/multiple-inheritance/glue.json | 16 +++++++++++ .../model/multiple-inheritance/other.json | 19 +++++++++++++ test/spec/extension.js | 28 +++++++++++++++++++ 4 files changed, 80 insertions(+) create mode 100644 test/fixtures/model/multiple-inheritance/base.json create mode 100644 test/fixtures/model/multiple-inheritance/glue.json create mode 100644 test/fixtures/model/multiple-inheritance/other.json diff --git a/test/fixtures/model/multiple-inheritance/base.json b/test/fixtures/model/multiple-inheritance/base.json new file mode 100644 index 0000000..aee872b --- /dev/null +++ b/test/fixtures/model/multiple-inheritance/base.json @@ -0,0 +1,17 @@ +{ + "name": "Multiple Inheritance Base", + "uri": "http://multiple-inheritance-base", + "prefix": "b", + "types": [ + { + "name": "Element", + "properties": [ + { "name": "ownedElement", "type": "b:Element", "isMany": true } + ] + }, + { + "name": "PackageableElement", + "superClass": [ "b:Element" ] + } + ] +} \ No newline at end of file diff --git a/test/fixtures/model/multiple-inheritance/glue.json b/test/fixtures/model/multiple-inheritance/glue.json new file mode 100644 index 0000000..0d45559 --- /dev/null +++ b/test/fixtures/model/multiple-inheritance/glue.json @@ -0,0 +1,16 @@ +{ + "name": "Multiple Inheritance Glue", + "uri": "http://multiple-inheritance-glue", + "prefix": "g", + "types": [ + { + "name": "Diagram", + "superClass": [ + "b:PackageableElement" + ], + "extends": [ + "o:Diagram" + ] + } + ] +} \ No newline at end of file diff --git a/test/fixtures/model/multiple-inheritance/other.json b/test/fixtures/model/multiple-inheritance/other.json new file mode 100644 index 0000000..e495581 --- /dev/null +++ b/test/fixtures/model/multiple-inheritance/other.json @@ -0,0 +1,19 @@ +{ + "name": "Multiple Inheritance Other", + "uri": "http://multiple-inheritance-other", + "prefix": "o", + "types": [ + { + "name": "DiagramElement", + "properties": [ + { "name": "ownedElement", "type": "DiagramElement", "isMany": true } + ] + }, + { + "name": "Diagram", + "superClass": [ + "DiagramElement" + ] + } + ] +} \ No newline at end of file diff --git a/test/spec/extension.js b/test/spec/extension.js index 82b2f8e..75d2dd2 100644 --- a/test/spec/extension.js +++ b/test/spec/extension.js @@ -224,6 +224,34 @@ describe('extension', function() { }); + + describe('name clashes', function() { + + var model = createModel([ + 'multiple-inheritance/base', + 'multiple-inheritance/other', + 'multiple-inheritance/glue' + ]); + + + it('should handle multiple inheritance through virtual package', function() { + + var baseElement = model.create('b:Element'); + + var otherElement = model.create('o:DiagramElement'); + + var diagram = model.create('o:Diagram', { + 'b:ownedElement': [ baseElement ], + ownedElement: [ otherElement ] + }); + + // then + expect(diagram.$instanceOf('b:Element')).to.be.true; + expect(diagram.$instanceOf('o:DiagramElement')).to.be.true; + }); + + }); + }); From 17b7c31b9649976b40eca9e8c588340bb09490fe Mon Sep 17 00:00:00 2001 From: Nico Rehwaldt Date: Wed, 21 Dec 2022 22:11:40 +0100 Subject: [PATCH 4/6] 7.0.0-exp.1 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8a87033..4cb4d63 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "moddle", - "version": "6.2.1", + "version": "7.0.0-exp.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "moddle", - "version": "6.2.1", + "version": "7.0.0-exp.1", "license": "MIT", "dependencies": { "min-dash": "^4.0.0" diff --git a/package.json b/package.json index 1d424f8..41db256 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "moddle", - "version": "6.2.1", + "version": "7.0.0-exp.1", "description": "A library for importing meta-model based file formats into JS", "scripts": { "all": "run-s lint test distro", From 07f8e2a6c8bc007ae7bacecb66996c74d9a43819 Mon Sep 17 00:00:00 2001 From: Nico Rehwaldt Date: Thu, 23 Feb 2023 14:49:55 +0100 Subject: [PATCH 5/6] 7.0.0-exp.2 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4cb4d63..005747d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "moddle", - "version": "7.0.0-exp.1", + "version": "7.0.0-exp.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "moddle", - "version": "7.0.0-exp.1", + "version": "7.0.0-exp.2", "license": "MIT", "dependencies": { "min-dash": "^4.0.0" diff --git a/package.json b/package.json index 41db256..03a1109 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "moddle", - "version": "7.0.0-exp.1", + "version": "7.0.0-exp.2", "description": "A library for importing meta-model based file formats into JS", "scripts": { "all": "run-s lint test distro", From d0dc45850ffaf65f196cfa6112eb35b2b5683270 Mon Sep 17 00:00:00 2001 From: Nico Rehwaldt Date: Fri, 24 Feb 2023 10:03:02 +0100 Subject: [PATCH 6/6] 7.0.0-exp.3 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 005747d..87fe5a3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "moddle", - "version": "7.0.0-exp.2", + "version": "7.0.0-exp.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "moddle", - "version": "7.0.0-exp.2", + "version": "7.0.0-exp.3", "license": "MIT", "dependencies": { "min-dash": "^4.0.0" diff --git a/package.json b/package.json index 03a1109..908d280 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "moddle", - "version": "7.0.0-exp.2", + "version": "7.0.0-exp.3", "description": "A library for importing meta-model based file formats into JS", "scripts": { "all": "run-s lint test distro",