Commit 7c1d9ce6 authored by Nicolas Biri's avatar Nicolas Biri

Keep information about flexibility in Class + element refresh

parent 3ae672da
Pipeline #847 passed with stage
in 1 minute and 24 seconds
......@@ -53,7 +53,7 @@ let conformsTo, generateId
* @param {Object} attributes - the references of the class.
* @constructor
*/
function Class(name, superClasses, attributes, references) {
function Class(name, superClasses, attributes, references, flexible) {
function jsmfElement(attr) {
Object.defineProperties(this,
{ __jsmf__: {value: elementMeta(jsmfElement)}
......@@ -62,11 +62,16 @@ function Class(name, superClasses, attributes, references) {
createReferences(this, jsmfElement)
_.forEach(attr, (v,k) => {this[k] = v})
}
jsmfElement.prototype.conformsTo = function () { return conformsTo(this) }
jsmfElement.prototype.getAssociated = getAssociated
Object.defineProperties(jsmfElement.prototype, {
conformsTo: {value: function () { return conformsTo(this) }, enumerable: false},
getAssociated : {value: getAssociated, enumerable: false}
})
superClasses = superClasses || []
superClasses = superClasses instanceof Array ? superClasses : [superClasses]
superClasses = _.isArray(superClasses) ? superClasses : [superClasses]
Object.assign(jsmfElement, {__name: name, superClasses, attributes: {}, references: {}})
jsmfElement.errorCallback = flexible
? onError.silent
: onError.throw
Object.defineProperty(jsmfElement, '__jsmf__', {value: classMeta()})
populateClassFunction(jsmfElement)
if (attributes !== undefined) { jsmfElement.addAttributes(attributes)}
......@@ -139,9 +144,9 @@ function addReference(name, target, sourceCardinality, opposite, oppositeCardina
target.references[opposite].cardinality :
Cardinality.check(oppositeCardinality)
, opposite: name
, errorCallback: oppositeErrorCallback === undefined && target.references[opposite] !== undefined ?
target.references[opposite].oppositeErrorCallback :
onError.throw
, errorCallback: oppositeErrorCallback === undefined && target.references[opposite] !== undefined
? target.references[opposite].oppositeErrorCallback
: this.errorCallback
}
}
if (associated !== undefined) {
......@@ -150,7 +155,7 @@ function addReference(name, target, sourceCardinality, opposite, oppositeCardina
target.references[opposite].associated = associated
}
}
this.references[name].errorCallback = errorCallback || onError.throw
this.references[name].errorCallback = errorCallback || this.errorCallback
}
function removeReference(name, opposite) {
......@@ -197,7 +202,7 @@ function addAttribute(name, type, mandatory, errorCallback) {
this.attributes[name] =
{ type: Type.normalizeType(type)
, mandatory: mandatory || false
, errorCallback: errorCallback || onError.throw
, errorCallback: errorCallback || this.errorCallback
}
}
......@@ -239,18 +244,20 @@ function createAttribute(o, name, desc) {
{ get: () => o.__jsmf__.attributes[name]
, set: o[setName(name)]
, enumerable: true
, configurable: true
}
)
}
function createSetAttribute(o, name, desc) {
Object.defineProperty(o, setName(name),
{value: x => {
{ value: x => {
if (!desc.type(x) && (desc.mandatory || !_.isUndefined(x))) {
desc.errorCallback(o, name, x)
}
o.__jsmf__.attributes[name] = x
}
, configurable: true
})
}
......@@ -285,6 +292,7 @@ function createReference(o, name, desc) {
o.__jsmf__.references[name] = xs
}
, enumerable: true
, configurable: true
})
}
......@@ -314,6 +322,7 @@ function createAddReference(o, name, desc) {
})
}
}
, configurable: true
})
}
......@@ -325,6 +334,7 @@ function createRemoveReference(o, name) {
associationMap[name] = _.differenceWith(associationMap[name], xs, (x,y) => x.elem === y)
o.__jsmf__.references[name] = _.difference(o.__jsmf__.references[name], xs)
}
, configurable: true
})
}
......@@ -334,13 +344,9 @@ function classMeta() {
}
function setFlexible(b) {
if (b) {
_.forEach(this.references, r => r.errorCallback = onError.silent)
_.forEach(this.attributes, r => r.errorCallback = onError.silent)
} else {
_.forEach(this.references, r => r.errorCallback = onError.throw)
_.forEach(this.attributes, r => r.errorCallback = onError.throw)
}
this.errorCallback = b ? onError.silent : onError.throw
_.forEach(this.references, r => r.errorCallback = this.errorCallback)
_.forEach(this.attributes, r => r.errorCallback = this.errorCallback)
}
function elementMeta(constructor) {
......@@ -369,10 +375,24 @@ function prefixedName(pre, n) {
return pre + _.upperFirst(n)
}
function refresh(o) {
const mm = o.conformsTo()
if (!mm) {return o}
const oBackup = Object.assign({}, o)
for (let x in o) {delete o[x]}
createAttributes(o, mm)
createReferences(o, mm)
for (let x in oBackup) {
o[x] = oBackup[x]
}
return o
}
const onError =
{ 'throw': (o,n,x) => {throw new TypeError(`Invalid assignment: ${x} for property ${n} of object ${o}`)}
, 'log': (o,n,x) => {console.log(`assignment: ${x} for property ${n} of object ${o}`)}
, 'silent': () => undefined
{ 'throw': function(o,n,x) {throw new TypeError(`Invalid assignment: ${x} for property ${n} of object ${o}`)}
, 'log': function(o,n,x) {console.log(`assignment: ${x} for property ${n} of object ${o}`)}
, 'silent': function() {}
}
module.exports = { Class, isJSMFClass, hasClass, onError }
module.exports = { Class, isJSMFClass, hasClass, onError, refresh }
......@@ -382,38 +382,37 @@ describe('Class', function() {
describe('Class getAllReferences', function() {
it('returns the references of the class if no inheritance', function(done) {
var State = new Class('State')
State.attributes.should.be.empty
State.addReference('next', State)
State.getAllReferences().should.have.property('next')
done()
it('returns the references of the class if no inheritance', done => {
var State = new Class('State')
State.attributes.should.be.empty
State.addReference('next', State)
State.getAllReferences().should.have.property('next')
done()
})
it('get parents references, override them if necessary', function(done) {
var State = new Class('State')
var TargetState = new Class('TargetState', State)
State.attributes.should.be.empty()
State.addAttributes({name: JSMF.String, age: Number})
State.addReference('next', State)
TargetState.addReference('next', TargetState)
var references = TargetState.getAllReferences()
references.should.have.property('next')
references.next.type.should.eql(TargetState)
done()
it('get parents references, override them if necessary', done => {
var State = new Class('State')
var TargetState = new Class('TargetState', State)
State.attributes.should.be.empty()
State.addAttributes({name: JSMF.String, age: Number})
State.addReference('next', State)
TargetState.addReference('next', TargetState)
var references = TargetState.getAllReferences()
references.should.have.property('next')
references.next.type.should.eql(TargetState)
done()
})
it ('kept the references of the last inherited class', function(done) {
const A = new Class('A')
A.addReference('foo', A)
const B = new Class('B')
B.addReference('foo', B)
const C = new Class('C', [A, B])
const references = C.getAllReferences()
references.should.have.property('foo')
references.foo.type.should.eql(B)
done()
it ('kept the references of the last inherited class', done => {
const A = new Class('A')
A.addReference('foo', A)
const B = new Class('B')
B.addReference('foo', B)
const C = new Class('C', [A, B])
const references = C.getAllReferences()
references.should.have.property('foo')
references.foo.type.should.eql(B)
done()
})
})
})
'use strict'
const should = require('should')
, _ = require('lodash')
, JSMF = require('../src/index')
, Class = JSMF.Class
describe('Class Flexibility', () => {
describe('throw callback', () => {
describe('errorCallback', () => {
describe('throw callback', () => {
it('throws error on invalid attribute value', done => {
const A = new Class('A', [], {x: {type: Number, errorCallback: JSMF.onError.throw}})
let test = function() {new A({x: 'foo'})}
test.should.throw()
done()
})
it('throws error on invalid reference value', done => {
const A = new Class('A', [])
const B = new Class('B', [])
A.addReference('x', A, 1, undefined, undefined, undefined, JSMF.onError.throw)
let test = function() {let x = new A(); let y = new B(); x.x = y}
test.should.throw()
done()
})
it('throws error on invalid reference value', done => {
const A = new Class('A', [])
A.addReference('x', A, 1, undefined, undefined, undefined, JSMF.onError.throw)
let test = function() {let x = new A(); x.x = 'toto'}
test.should.throw()
done()
})
it('throws error on invalid attribute value', done => {
const A = new Class('A', [], {x: {type: Number, errorCallback: JSMF.onError.throw}})
let test = function() {new A({x: 'foo'})}
test.should.throw()
done()
})
it('throws error on invalid reference value', done => {
const A = new Class('A', [])
const B = new Class('B', [])
A.addReference('x', A, 1, undefined, undefined, undefined, JSMF.onError.throw)
let test = function() {let x = new A(); let y = new B(); x.x = y}
test.should.throw()
done()
})
describe('silent callback', () => {
it('assigns on invalid attribute value', done => {
const A = new Class('A', [], {x: {type: Number, errorCallback: JSMF.onError.silent}})
let x = new A({x: 12})
x.x = 'toto'
x.x.should.be.equal('toto')
done()
})
it('assigns on invalid reference value', done => {
const A = new Class('A', [])
const B = new Class('B', [])
A.addReference('x', A, 1, undefined, undefined, undefined, JSMF.onError.silent)
let x = new A()
x.x = x
let y = new B()
x.x = y
x.x.should.be.eql([y])
done()
})
it('assign invalid reference value', done => {
const A = new Class('A', [])
A.addReference('x', A, 1, undefined, undefined, undefined, JSMF.onError.silent)
let x = new A()
x.x = x
x.x = 'toto'
x.x.should.be.eql(['toto'])
done()
})
it('throws error on invalid reference value', done => {
const A = new Class('A', [])
A.addReference('x', A, 1, undefined, undefined, undefined, JSMF.onError.throw)
let test = function() {let x = new A(); x.x = 'toto'}
test.should.throw()
done()
})
})
describe('default behaviour', () => {
describe('silent callback', () => {
it('throws on invalid attribute value', done => {
const A = new Class('A', [], {x: Number})
let test = function() {new A({x: 'foo'})}
test.should.throw()
done()
})
it('assigns on invalid attribute value', done => {
const A = new Class('A', [], {x: {type: Number, errorCallback: JSMF.onError.silent}})
let x = new A({x: 12})
x.x = 'toto'
x.x.should.be.equal('toto')
done()
})
it('throws on invalid reference value', done => {
const A = new Class('A', [])
const B = new Class('B', [])
A.addReference('x', A, 1)
let test = function() {let x = new A(); let y = new B(); x.x = y}
test.should.throw()
done()
})
it('assigns on invalid reference value', done => {
const A = new Class('A', [])
const B = new Class('B', [])
A.addReference('x', A, 1, undefined, undefined, undefined, JSMF.onError.silent)
let x = new A()
x.x = x
let y = new B()
x.x = y
x.x.should.be.eql([y])
done()
})
it('assign invalid reference value', done => {
const A = new Class('A', [])
A.addReference('x', A, 1, undefined, undefined, undefined, JSMF.onError.silent)
let x = new A()
x.x = x
x.x = 'toto'
x.x.should.be.eql(['toto'])
done()
describe('change flexibility behaviour to strict', () => {
it('throws on invalid attribute value', done => {
const A = new Class('A', [], {x: {type: Number, errorCallback: JSMF.onError.silent}})
A.setFlexible(false)
let test = function() {new A({x: 'foo'})}
test.should.throw()
done()
})
it('throws on invalid reference value', done => {
const A = new Class('A', [])
const B = new Class('B', [])
A.addReference('x', A, 1, undefined, undefined, undefined, JSMF.onError.silent)
A.setFlexible(false)
let test = function() {let x = new A(); let y = new B(); x.x = y}
test.should.throw()
done()
})
})
describe('change flexibility behaviour to flexible', () => {
it('assigns on invalid attribute value', done => {
const A = new Class('A', [], {x: {type: Number, errorCallback: JSMF.onError.throw}})
A.setFlexible(true)
let x = new A({x: 12})
x.x = 'toto'
x.x.should.be.equal('toto')
done()
})
it('assigns on invalid reference value', done => {
const A = new Class('A', [])
const B = new Class('B', [])
A.addReference('x', A, 1, undefined, undefined, undefined, JSMF.onError.throw)
A.setFlexible(true)
let x = new A()
x.x = x
let y = new B()
x.x = y
x.x.should.be.eql([y])
done()
})
})
})
describe('default behaviour', () => {
describe('chanching the metamodel', () => {
it('throws on invalid attribute value', done => {
const A = new Class('A', [], {x: Number})
let test = function() {new A({x: 'foo'})}
test.should.throw()
it('ensures that removed property is not acessible via getAllAttributes', done => {
const A = new Class('A', [], {a: Number})
const a = new A({a: 12})
a.b = 'foo'
delete A.attributes['a']
_.pick(a, _.keys(A.getAllAttributes())).should.eql({})
done()
})
it('throws on invalid reference value', done => {
const A = new Class('A', [])
const B = new Class('B', [])
A.addReference('x', A, 1)
let test = function() {let x = new A(); let y = new B(); x.x = y}
test.should.throw()
it('ensures that added property is acessible via getAllAttributes', done => {
const A = new Class('A', [], {a: Number})
const a = new A({a: 12})
a.b = 'foo'
_.pick(a, _.keys(A.getAllAttributes())).should.eql({a: 12})
delete A.addAttribute('b', String)
_.pick(a, _.keys(A.getAllAttributes())).should.eql({a: 12, b: 'foo'})
done()
})
})
describe('change flexibility behaviour to strict', () => {
it('throws on invalid attribute value', done => {
const A = new Class('A', [], {x: {type: Number, errorCallback: JSMF.onError.silent}})
A.setFlexible(false)
let test = function() {new A({x: 'foo'})}
it('throws error on a new attribute wrong type when refresh', done => {
const A = new Class('A', [], {a: Number})
const a = new A({a: 12})
delete A.addAttribute('b', String)
JSMF.refresh(a)
function test() {a.b = 42}
test.should.throw()
done()
})
it('throws on invalid reference value', done => {
const A = new Class('A', [])
const B = new Class('B', [])
A.addReference('x', A, 1, undefined, undefined, undefined, JSMF.onError.silent)
A.setFlexible(false)
let test = function() {let x = new A(); let y = new B(); x.x = y}
it('except if the class is flexible', done => {
const A = new Class('A', [], {a: Number})
const a = new A({a: 12})
delete A.addAttribute('b', String)
JSMF.refresh(a)
function test() {a.b = 42}
test.should.throw()
done()
})
})
describe('change flexibility behaviour to flexible', () => {
it('assigns on invalid attribute value', done => {
const A = new Class('A', [], {x: {type: Number, errorCallback: JSMF.onError.throw}})
A.setFlexible(true)
let x = new A({x: 12})
x.x = 'toto'
x.x.should.be.equal('toto')
it ('adds flexible attributes to a flexible class', done => {
const A = new Class('A', [], {a: Number}, {}, true)
const a = new A({a: 12})
delete A.addAttribute('b', String)
JSMF.refresh(a)
function test() {a.b = 42}
test.should.not.throw()
done()
})
it('assigns on invalid reference value', done => {
const A = new Class('A', [])
const B = new Class('B', [])
A.addReference('x', A, 1, undefined, undefined, undefined, JSMF.onError.throw)
A.setFlexible(true)
let x = new A()
x.x = x
let y = new B()
x.x = y
x.x.should.be.eql([y])
it ('adds flexible references to a flexible class', done => {
const A = new Class('A', [], {a: Number}, {}, true)
const B = new Class('B')
const a = new A({a: 12})
delete A.addReference('b', B)
JSMF.refresh(a)
function test() {a.b = new A()}
test.should.not.throw()
function testAddFunction() {a.addB(new A())}
testAddFunction.should.not.throw()
done()
})
})
})
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment