diff --git a/lib/moddle.js b/lib/moddle.js index 64bfd47..0b27fa6 100644 --- a/lib/moddle.js +++ b/lib/moddle.js @@ -35,8 +35,10 @@ import { * var moddle = new Moddle([pkg]); * * @param {Array} packages the packages to contain + * + * @param { { strict?: boolean } } [config] moddle configuration */ -export default function Moddle(packages) { +export default function Moddle(packages, config = {}) { this.properties = new Properties(this); @@ -44,6 +46,8 @@ export default function Moddle(packages) { this.registry = new Registry(packages, this.properties); this.typeCache = {}; + + this.config = config; } diff --git a/lib/properties.js b/lib/properties.js index 513a0b1..f57e8c4 100644 --- a/lib/properties.js +++ b/lib/properties.js @@ -27,7 +27,7 @@ Properties.prototype.set = function(target, name, value) { throw new TypeError('property name must be a non-empty string'); } - var property = this.model.getPropertyDescriptor(target, name); + var property = this.getProperty(target, name); var propertyName = property && property.name; @@ -38,7 +38,7 @@ Properties.prototype.set = function(target, name, value) { if (property) { delete target[propertyName]; } else { - delete target.$attrs[name]; + delete target.$attrs[stripGlobal(name)]; } } else { @@ -51,7 +51,7 @@ Properties.prototype.set = function(target, name, value) { defineProperty(target, property, value); } } else { - target.$attrs[name] = value; + target.$attrs[stripGlobal(name)] = value; } } }; @@ -66,10 +66,10 @@ Properties.prototype.set = function(target, name, value) { */ Properties.prototype.get = function(target, name) { - var property = this.model.getPropertyDescriptor(target, name); + var property = this.getProperty(target, name); if (!property) { - return target.$attrs[name]; + return target.$attrs[stripGlobal(name)]; } var propertyName = property.name; @@ -123,6 +123,44 @@ Properties.prototype.defineModel = function(target, model) { this.define(target, '$model', { value: model }); }; +/** + * Return property with the given name on the element. + * + * @param {any} target + * @param {string} name + * + * @return {object | null} property + */ +Properties.prototype.getProperty = function(target, name) { + + var model = this.model; + + var property = model.getPropertyDescriptor(target, name); + + if (property) { + return property; + } + + if (name.includes(':')) { + return null; + } + + const strict = model.config.strict; + + if (typeof strict !== 'undefined') { + const error = new TypeError(`unknown property <${ name }> on <${ target.$type }>`); + + if (strict) { + throw error; + } else { + + // eslint-disable-next-line no-undef + typeof console !== 'undefined' && console.warn(error); + } + } + + return null; +}; function isUndefined(val) { return typeof val === 'undefined'; @@ -135,4 +173,8 @@ function defineProperty(target, property, value) { value: value, configurable: true }); +} + +function stripGlobal(name) { + return name.replace(/^:/, ''); } \ No newline at end of file diff --git a/test/helper.js b/test/helper.js index 7056f89..550a6d1 100644 --- a/test/helper.js +++ b/test/helper.js @@ -21,7 +21,7 @@ export function createModelBuilder(base) { throw new Error('[test-util] must specify a base directory'); } - function createModel(packageNames) { + function createModel(packageNames, config = {}) { var packages = map(packageNames, function(f) { var pkg = cache[f]; @@ -38,7 +38,7 @@ export function createModelBuilder(base) { return pkg; }); - return new Moddle(packages); + return new Moddle(packages, config); } return createModel; diff --git a/test/spec/moddle.js b/test/spec/moddle.js index 63e53cf..b80f55b 100644 --- a/test/spec/moddle.js +++ b/test/spec/moddle.js @@ -356,6 +356,38 @@ describe('moddle', function() { expect(element.get('props:count')).to.eql(10); }); + + it('should access global name', function() { + + // when + const element = moddle.create('props:ComplexCount', { + ':xmlns': 'http://foo' + }); + + // then + expect(element.get(':xmlns')).to.eql('http://foo'); + expect(element.get('xmlns')).to.eql('http://foo'); + + // available as extension attribute + expect(element.$attrs).to.have.property('xmlns'); + }); + + + it('should access global name (no prefix)', function() { + + // when + const element = moddle.create('props:ComplexCount', { + 'xmlns': 'http://foo' + }); + + // then + expect(element.get(':xmlns')).to.eql('http://foo'); + expect(element.get('xmlns')).to.eql('http://foo'); + + // available as extension attribute + expect(element.$attrs).to.have.property('xmlns'); + }); + }); @@ -376,4 +408,115 @@ describe('moddle', function() { }); + + describe('property access (lax)', function() { + + const moddle = createModel([ + 'properties' + ], { + strict: false + }); + + + describe('typed', function() { + + it('should access unknown attribute', function() { + + // when + const element = moddle.create('props:ComplexCount', { + foo: 'bar' + }); + + // then + expect(element.get('foo')).to.eql('bar'); + }); + + }); + + }); + + + describe('property access (strict)', function() { + + const moddle = createModel([ + 'properties' + ], { + strict: true + }); + + + it('should configure in strict mode', function() { + + // then + expect(moddle.config.strict).to.be.true; + }); + + + describe('typed', function() { + + it('should access basic', function() { + + // when + const element = moddle.create('props:ComplexCount', { + count: 10 + }); + + // then + expect(element.get('count')).to.eql(10); + expect(element.get('props:count')).to.eql(10); + + // available under base name + expect(element.count).to.exist; + }); + + + it('should access global name', function() { + + // when + const element = moddle.create('props:ComplexCount', { + ':xmlns': 'http://foo' + }); + + // then + expect(element.get(':xmlns')).to.eql('http://foo'); + + expect(() => { + element.get('xmlns'); + }).to.throw(/unknown property on /); + + // available as extension attribute + expect(element.$attrs).to.have.property('xmlns'); + }); + + + it('fail accessing unknown property', function() { + + // when + const element = moddle.create('props:ComplexCount'); + + // then + expect(() => { + element.get('foo'); + }).to.throw(/unknown property on /); + + expect(() => { + element.set('foo', 10); + }).to.throw(/unknown property on /); + }); + + + it('fail instantiating with unknown property', function() { + + // then + expect(() => { + moddle.create('props:ComplexCount', { + foo: 10 + }); + }).to.throw(/unknown property on /); + }); + + }); + + }); + });