UNPKG

cloudboost-tv

Version:

Database Service that does Storage, Search, Real-time and a whole lot more.

237 lines (211 loc) 6.65 kB
var AssertionError = require('./assertion-error'); var util = require('./util'); var format = require('should-format'); /** * should Assertion * @param {*} obj Given object for assertion * @constructor * @memberOf should * @static */ function Assertion(obj) { this.obj = obj; this.anyOne = false; this.negate = false; this.params = {actual: obj}; } /** * Way to extend Assertion function. It uses some logic * to define only positive assertions and itself rule with negative assertion. * * All actions happen in subcontext and this method take care about negation. * Potentially we can add some more modifiers that does not depends from state of assertion. * @memberOf Assertion * @category assertion * @static * @param {String} name Name of assertion. It will be used for defining method or getter on Assertion.prototype * @param {Function} func Function that will be called on executing assertion * @param {Boolean} [isGetter] If this assertion is getter. By default it is false. * @example * * Assertion.add('asset', function() { * this.params = { operator: 'to be asset' }; * * this.obj.should.have.property('id').which.is.a.Number; * this.obj.should.have.property('path'); * }); */ Assertion.add = function(name, func, isGetter) { var prop = {enumerable: true, configurable: true}; isGetter = !!isGetter; prop[isGetter ? 'get' : 'value'] = function() { var context = new Assertion(this.obj, this, name); context.anyOne = this.anyOne; try { func.apply(context, arguments); } catch(e) { //check for fail if(e instanceof AssertionError) { //negative fail if(this.negate) { this.obj = context.obj; this.negate = false; return this.proxied(); } //console.log('catch', name, context.params.operator, e.operator); //console.log(name, e.actual, context.obj, context.params.actual, this.params.actual); /*if(e.operator !== context.params.operator) {// it means assertion happen because own context if(!('obj' in context.params)) { if(!('actual' in context.params)) { context.params.actual = context.obj; } } util.merge(e, context.params); //e.operato //e.operator = context.params.operator; }*/ if(context != e.assertion) { context.params.previous = e; } //positive fail context.negate = false; context.fail(); } // throw if it is another exception throw e; } //negative pass if(this.negate) { context.negate = true;//because .fail will set negate context.params.details = "false negative fail"; context.fail(); } //positive pass if(!this.params.operator) this.params = context.params;//shortcut this.obj = context.obj; this.negate = false; return this.proxied(); }; Object.defineProperty(Assertion.prototype, name, prop); }; Assertion.addChain = function(name, onCall) { onCall = onCall || function() { }; Object.defineProperty(Assertion.prototype, name, { get: function() { onCall(); return this.proxied(); }, enumerable: true }); }; /** * Create alias for some `Assertion` property * * @memberOf Assertion * @category assertion * @static * @param {String} from Name of to map * @param {String} to Name of alias * @example * * Assertion.alias('true', 'True'); */ Assertion.alias = function(from, to) { var desc = Object.getOwnPropertyDescriptor(Assertion.prototype, from); if(!desc) throw new Error('Alias ' + from + ' -> ' + to + ' could not be created as ' + from + ' not defined'); Object.defineProperty(Assertion.prototype, to, desc); }; Assertion.prototype = { constructor: Assertion, /** * Base method for assertions. Before calling this method need to fill Assertion#params object. This method usually called from other assertion methods. * `Assertion#params` can contain such properties: * * `operator` - required string containing description of this assertion * * `obj` - optional replacement for this.obj, it usefull if you prepare more clear object then given * * `message` - if this property filled with string any others will be ignored and this one used as assertion message * * `expected` - any object used when you need to assert relation between given object and expected. Like given == expected (== is a relation) * * `details` - additional string with details to generated message * * @memberOf Assertion * @category assertion * @param {*} expr Any expression that will be used as a condition for asserting. * @example * * var a = new should.Assertion(42); * * a.params = { * operator: 'to be magic number', * } * * a.assert(false); * //throws AssertionError: expected 42 to be magic number */ assert: function(expr) { if(expr) return this.proxied(); var params = this.params; if('obj' in params && !('actual' in params)) { params.actual = params.obj; } else if(!('obj' in params) && !('actual' in params)) { params.actual = this.obj; } params.stackStartFunction = params.stackStartFunction || this.assert; params.negate = this.negate; params.assertion = this; throw new AssertionError(params); }, /** * Shortcut for `Assertion#assert(false)`. * * @memberOf Assertion * @category assertion * @example * * var a = new should.Assertion(42); * * a.params = { * operator: 'to be magic number', * } * * a.fail(); * //throws AssertionError: expected 42 to be magic number */ fail: function() { return this.assert(false); }, /** * Negation modifier. Current assertion chain become negated. Each call invert negation on current assertion. * * @memberOf Assertion * @category assertion */ get not() { this.negate = !this.negate; return this.proxied(); }, /** * Any modifier - it affect on execution of sequenced assertion to do not `check all`, but `check any of`. * * @memberOf Assertion * @category assertion */ get any() { this.anyOne = true; return this.proxied(); }, proxied: function() { if(typeof Proxy == 'function') { return new Proxy(this, { get: function(target, name) { if(name in target) { return target[name]; } else { throw new Error('Assertion has no property ' + util.formatProp(name)); } } }) } return this; } }; module.exports = Assertion;