diff --git a/README.mkd b/README.mkd index ccdcc35..bc79964 100644 --- a/README.mkd +++ b/README.mkd @@ -4,25 +4,27 @@ Backbone Factory Introduction ------------ -Backbone Factory is a small javascript library for creating [Backbone.js](http://documentcloud.github.com/backbone/) objects for testing your code. It has no external dependency. +Backbone Factory is a small javascript library for creating [Backbone.js](http://documentcloud.github.com/backbone/) objects for testing your code. We have extended [this](https://github.com/SupportBee/Backbone-Factory) library to provide support from nested models/collections. This is a strict superset of the original authors functionality. It has no external dependency. -The API is heavily inspired by the awesome [Factory Girl](https://github.com/thoughtbot/factory_girl). +The API is heavily inspired by the awesome [Factory Girl](https://github.com/thoughtbot/factory_girl). Installation ------------ -To use it, just [download](https://github.com/SupportBee/Backbone-Factory/raw/master/public/javascripts/backbone-factory.js) the file and include it in your testing setup. +To use it, just [download](https://github.com/recruiterbox/Backbone-Factory/raw/master/public/javascripts/backbone-factory.js) the file and include it in your testing setup. Usage ----- -Lets say you have two Backbone models, Post and User +Let's say you have two Backbone models - Post and User ```javascript var User = Backbone.Model.extend({ - name: null, - email: null + defaults: { + name: 'Raghu', + email: 'raghu@example.com' + } }); @@ -40,57 +42,171 @@ var Post = Backbone.Model.extend({ To define factories for them ```javascript -var postFactory = BackboneFactory.define('post', Post); -var userFactory = BackboneFactory.define('user', User); +BackboneFactory.define('post', Post); +BackboneFactory.define('user', User); ``` ### Using Factories -To use these factories, +To use these factories, -```javscript -this.postObject = BackboneFactory.create('post'); -this.userObject = BackboneFactory.create('user'); +```javascript +var postObject = BackboneFactory.create('post'); +var userObject = BackboneFactory.create('user'); ``` -This will create objects using the [defaults](http://documentcloud.github.com/backbone/#Model-defaults) you have in your class definitions. +This will create objects using the [defaults](http://documentcloud.github.com/backbone/#Model-defaults) you have in your class definitions. It is easy to override the defaults when creating an object +```javascript + +var userObject = BackboneFactory.create('user', function(){ + return { + name: "Custom Name", + email: "custom@email.com" + }; +}); +``` +### Defining and using Collections -### Defining Sequences +To define factories for collections ```javascript -var emailSequence = BackboneFactory.define_sequence('email', function(n){ - return "person"+n+"@example.com"; - }); +var Posts = Backbone.Collection.extend({ + model: Post +}); +var Users = Backbone.Collection.extend({ + model: User +}); + +BackboneFactory.define_collection('posts', Posts, 3); //Third arugement is the default size of the collection +BackboneFactory.define_collection('users', Users, 4); + +var postsCollection = BackboneFactory.create_collection('posts'); //Creates 3 posts +var usersCollection = BackboneFactory.create_collection('users',2) //Creates only 2 users overriding the default of 4 + ``` -### Using Sequences + +### Schema Support + +If your backbone models define a schema, it is used to set default values. Nested models are also supported. ```javascript -var email = BackboneFactory.next('email') // person1@example.com + +var PostWithSchema = Backbone.Model.extend({ + + schema: { + title: { + type: 'string', + default: 'Default value from schema' + }, + body: { + type: 'string', + default: 'Default body' + }, + author: { + type: 'related', + _constructor: User + } + } + +}); + + +BackboneFactory.define('post_with_schema', PostWithSchema); +var post = BackboneFactory.create('post_with_schema'); + +//Post title will be picked up from schema +console.log(post.get('title'); //Default value from schema + +//Author was created automatically using the schema +var author = post.get('author'); +console.log(author.get('name'); //Raghu + +``` +Cool! isn't it? If defaults and schema are both present, default values from the backbone model take precedence over the schema defaults. +Now, lets complicate this a bit further. + +```javascript +var Comment = Backbone.Model.extend({ + + schema: { + msg: { + type: 'string', + default: 'Default comment msg' + } + } + +}); + +var Comments = Backbone.Collection.extend({ + model: Comment +}); + +var PostWithSchema = Backbone.Model.extend({ + + schema: { + title: { + type: 'string', + default: 'Default value from schema' + }, + body: { + type: 'string', + default: 'Default body' + }, + author: { + type: 'related', + _constructor: User + } + comments: { + type: 'related', + _constructor: Comments + } + } +}); + + +BackboneFactory.define('post_with_schema', PostWithSchema); +BackboneFactory.define('comments', Comments, 2); + +var post = BackboneFactory.create('post_with_schema'); +var comments = post.get('comments'); +console.log(comments.length) //gives a value of 2 ``` +Its very easy to change the size of the collection of comments -### Defining Factories with defaults +```javascript +var post = BackboneFactory.create('post_with_schema', function(){ + return { comments: BackboneFactory.create_collection('comments', 4) }; +}); +var comments = post.get('comments'); +console.log(comments.length) //gives a value of 4 + +``` +If a comments BackboneFactory is not defined, an empty collection of comments is created when a post is created. +### Dynamically generating attribute values: Sequences +Often, we'd like to generate unique values for model attributes when new objects are created. For example, we may want the username/email of a user to be always unique. We can use sequences for this. ```javascript -var userFactory = BackboneFactory.define('user', User, function(){ - return { - name : 'Backbone User', - email: BackboneFactory.next('person_email') - }; - } - ); +BackboneFactory.define_sequence('email', function(n){ + return "person"+n+"@example.com"; +}); + +var email = BackboneFactory.next('email') // person1@example.com ``` -### Overriding defaults when creating objects +### Defining Factories with Sequences ```javascript -var userWithEmail = BackboneFactory.create('user', function(){ - return { - email: 'overriden@example.com' - }; - }); - ``` +BackboneFactory.define('user', User, function(){ + return { + name : 'Backbone User', + email: BackboneFactory.next('email') + }; +}); +var user = BackboneFactory.create('user); +console.log(user.get('email') // person2@example.com +``` Contributing ------------ @@ -129,7 +245,3 @@ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - - - diff --git a/public/javascripts/backbone-factory.js b/public/javascripts/backbone-factory.js index 5acec29..13c0ad3 100644 --- a/public/javascripts/backbone-factory.js +++ b/public/javascripts/backbone-factory.js @@ -1,10 +1,30 @@ +/*global BackboneFactory */ + // Backbone Factory JS -// https://github.com/SupportBee/Backbone-Factory +// https://github.com/Aplopio/Backbone-Factory (function(){ + + function get_factory_name(klass){ + var keys = _.keys(BackboneFactory.model_klasses), + values = _.values(BackboneFactory.model_klasses); + return keys[values.indexOf(klass)]; + } + + function get_collection_name(klass){ + var keys = _.keys(BackboneFactory.collection_klasses), + values = _.values(BackboneFactory.collection_klasses); + return keys[values.indexOf(klass)]; + } + window.BackboneFactory = { factories: {}, + model_klasses: {}, + + collections: {}, + collection_klasses: {}, + sequences: {}, define: function(factory_name, klass, defaults){ @@ -14,32 +34,66 @@ throw "Factory name should not contain spaces or other funky characters"; } - if(defaults === undefined) defaults = function(){return {}}; + if(defaults === undefined) defaults = function(){return {};} // The object creator - this.factories[factory_name] = function(options){ - if(options === undefined) options = function(){return {}}; - arguments = _.extend({}, {id: BackboneFactory.next("_" + factory_name + "_id")}, defaults.call(), options.call()); - return new klass(arguments); + this.model_klasses[factory_name] = klass; + this.factories[factory_name] = function(attrs_generator, options){ + if(attrs_generator === undefined) attrs_generator = function(){return {};}; + + var schema = klass.prototype.schema, + default_vals = _.result(klass.prototype, 'defaults') || {}; + + if(schema){ + default_vals = _(default_vals).clone(); + + _(schema).each(function(attr, key){ + if(_(attr).has('default') && default_vals[key] === undefined) { + default_vals[key] = attr.default; + } + }); + + _(schema).each(function(attr, key){ + var name; + if(attr.type == 'related' && attr._constructor && !(key in default_vals)){ + if(name = get_factory_name(attr._constructor)){ + default_vals[key] = BackboneFactory.create(name); + }else if(name = get_collection_name(attr._constructor)){ + default_vals[key] = BackboneFactory.create_collection(name); + }else{ + default_vals[key] = new attr._constructor(); + } + } + }); + + } + + var attributes = _.extend( + { id: BackboneFactory.next("_" + factory_name + "_id") }, + default_vals, + defaults.call(), + attrs_generator.call() + ); + return new klass(attributes, options); }; // Lets define a sequence for id BackboneFactory.define_sequence("_"+ factory_name +"_id", function(n){ - return n + return n; }); }, - create: function(factory_name, options){ + create: function(factory_name, attrs_generator, options){ if(this.factories[factory_name] === undefined){ throw "Factory with name " + factory_name + " does not exist"; } - return this.factories[factory_name].apply(null, [options]); + return this.factories[factory_name].call(null, attrs_generator, options); }, define_sequence: function(sequence_name, callback){ - this.sequences[sequence_name] = {} + this.sequences[sequence_name] = {}; this.sequences[sequence_name]['counter'] = 0; - this.sequences[sequence_name]['callback'] = callback; + this.sequences[sequence_name]['callback'] = callback; }, next: function(sequence_name){ @@ -47,7 +101,31 @@ throw "Sequence with name " + sequence_name + " does not exist"; } this.sequences[sequence_name]['counter'] += 1; - return this.sequences[sequence_name]['callback'].apply(null, [this.sequences[sequence_name]['counter']]); //= callback; + return this.sequences[sequence_name]['callback'].apply(null, [this.sequences[sequence_name]['counter']]); //= callback; + }, + + define_collection: function(collection_name, klass, default_size, default_options){ + var factory_name = get_factory_name(klass.prototype.model); + + this.collection_klasses[collection_name] = klass; + this.collections[collection_name] = function(size, attrs_generator, options){ + var models = []; + if(typeof size!='number') size = default_size; + options = options || default_options || {}; + + for(var i=0; i=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a, -c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);var e={computed:Infinity};h(a,function(a,b,f){b=c?c.call(d,a,b,f):a;bd?1:0}),"value")};b.sortedIndex=function(a,c,d){d||(d=b.identity);for(var e=0,f=a.length;e>1;d(a[g])=0})})};b.zip=function(){for(var a=f.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c), -e=0;e=0;d--)b=[a[d].apply(this,b)];return b[0]}};b.after=function(a,b){return function(){if(--a<1)return b.apply(this,arguments)}};b.keys=F||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var b=[],d;for(d in a)l.call(a,d)&&(b[b.length]=d);return b};b.values=function(a){return b.map(a, -b.identity)};b.functions=b.methods=function(a){return b.filter(b.keys(a),function(c){return b.isFunction(a[c])}).sort()};b.extend=function(a){h(f.call(arguments,1),function(b){for(var d in b)b[d]!==void 0&&(a[d]=b[d])});return a};b.defaults=function(a){h(f.call(arguments,1),function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};b.isEqual=function(a,c){if(a===c)return!0;var d=typeof a;if(d!= -typeof c)return!1;if(a==c)return!0;if(!a&&c||a&&!c)return!1;if(a._chain)a=a._wrapped;if(c._chain)c=c._wrapped;if(a.isEqual)return a.isEqual(c);if(b.isDate(a)&&b.isDate(c))return a.getTime()===c.getTime();if(b.isNaN(a)&&b.isNaN(c))return!1;if(b.isRegExp(a)&&b.isRegExp(c))return a.source===c.source&&a.global===c.global&&a.ignoreCase===c.ignoreCase&&a.multiline===c.multiline;if(d!=="object")return!1;if(a.length&&a.length!==c.length)return!1;d=b.keys(a);var e=b.keys(c);if(d.length!=e.length)return!1; -for(var f in a)if(!(f in c)||!b.isEqual(a[f],c[f]))return!1;return!0};b.isEmpty=function(a){if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(l.call(a,c))return!1;return!0};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=n||function(a){return E.call(a)==="[object Array]"};b.isArguments=function(a){return!(!a||!l.call(a,"callee"))};b.isFunction=function(a){return!(!a||!a.constructor||!a.call||!a.apply)};b.isString=function(a){return!!(a===""||a&&a.charCodeAt&&a.substr)}; -b.isNumber=function(a){return!!(a===0||a&&a.toExponential&&a.toFixed)};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===!0||a===!1};b.isDate=function(a){return!(!a||!a.getTimezoneOffset||!a.setUTCFullYear)};b.isRegExp=function(a){return!(!a||!a.test||!a.exec||!(a.ignoreCase||a.ignoreCase===!1))};b.isNull=function(a){return a===null};b.isUndefined=function(a){return a===void 0};b.noConflict=function(){p._=C;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e= -0;e/g,interpolate:/<%=([\s\S]+?)%>/g};b.template=function(a,c){var d=b.templateSettings;d="var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push('"+a.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(d.interpolate,function(a,b){return"',"+b.replace(/\\'/g,"'")+",'"}).replace(d.evaluate|| -null,function(a,b){return"');"+b.replace(/\\'/g,"'").replace(/[\r\n\t]/g," ")+"__p.push('"}).replace(/\r/g,"\\r").replace(/\n/g,"\\n").replace(/\t/g,"\\t")+"');}return __p.join('');";d=new Function("obj",d);return c?d(c):d};var j=function(a){this._wrapped=a};b.prototype=j.prototype;var r=function(a,c){return c?b(a).chain():a},H=function(a,c){j.prototype[a]=function(){var a=f.call(arguments);D.call(a,this._wrapped);return r(c.apply(b,a),this._chain)}};b.mixin(b);h(["pop","push","reverse","shift","sort", -"splice","unshift"],function(a){var b=i[a];j.prototype[a]=function(){b.apply(this._wrapped,arguments);return r(this._wrapped,this._chain)}});h(["concat","join","slice"],function(a){var b=i[a];j.prototype[a]=function(){return r(b.apply(this._wrapped,arguments),this._chain)}});j.prototype.chain=function(){this._chain=!0;return this};j.prototype.value=function(){return this._wrapped}})(); +// Underscore.js 1.5.1 +// http://underscorejs.org +// (c) 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors +// Underscore may be freely distributed under the MIT license. +!function(){var n=this,t=n._,r={},e=Array.prototype,u=Object.prototype,i=Function.prototype,a=e.push,o=e.slice,c=e.concat,l=u.toString,f=u.hasOwnProperty,s=e.forEach,p=e.map,v=e.reduce,h=e.reduceRight,d=e.filter,g=e.every,m=e.some,y=e.indexOf,b=e.lastIndexOf,x=Array.isArray,_=Object.keys,w=i.bind,j=function(n){return n instanceof j?n:this instanceof j?(this._wrapped=n,void 0):new j(n)};"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=j),exports._=j):n._=j,j.VERSION="1.5.1";var A=j.each=j.forEach=function(n,t,e){if(null!=n)if(s&&n.forEach===s)n.forEach(t,e);else if(n.length===+n.length){for(var u=0,i=n.length;i>u;u++)if(t.call(e,n[u],u,n)===r)return}else for(var a in n)if(j.has(n,a)&&t.call(e,n[a],a,n)===r)return};j.map=j.collect=function(n,t,r){var e=[];return null==n?e:p&&n.map===p?n.map(t,r):(A(n,function(n,u,i){e.push(t.call(r,n,u,i))}),e)};var E="Reduce of empty array with no initial value";j.reduce=j.foldl=j.inject=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),v&&n.reduce===v)return e&&(t=j.bind(t,e)),u?n.reduce(t,r):n.reduce(t);if(A(n,function(n,i,a){u?r=t.call(e,r,n,i,a):(r=n,u=!0)}),!u)throw new TypeError(E);return r},j.reduceRight=j.foldr=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),h&&n.reduceRight===h)return e&&(t=j.bind(t,e)),u?n.reduceRight(t,r):n.reduceRight(t);var i=n.length;if(i!==+i){var a=j.keys(n);i=a.length}if(A(n,function(o,c,l){c=a?a[--i]:--i,u?r=t.call(e,r,n[c],c,l):(r=n[c],u=!0)}),!u)throw new TypeError(E);return r},j.find=j.detect=function(n,t,r){var e;return O(n,function(n,u,i){return t.call(r,n,u,i)?(e=n,!0):void 0}),e},j.filter=j.select=function(n,t,r){var e=[];return null==n?e:d&&n.filter===d?n.filter(t,r):(A(n,function(n,u,i){t.call(r,n,u,i)&&e.push(n)}),e)},j.reject=function(n,t,r){return j.filter(n,function(n,e,u){return!t.call(r,n,e,u)},r)},j.every=j.all=function(n,t,e){t||(t=j.identity);var u=!0;return null==n?u:g&&n.every===g?n.every(t,e):(A(n,function(n,i,a){return(u=u&&t.call(e,n,i,a))?void 0:r}),!!u)};var O=j.some=j.any=function(n,t,e){t||(t=j.identity);var u=!1;return null==n?u:m&&n.some===m?n.some(t,e):(A(n,function(n,i,a){return u||(u=t.call(e,n,i,a))?r:void 0}),!!u)};j.contains=j.include=function(n,t){return null==n?!1:y&&n.indexOf===y?n.indexOf(t)!=-1:O(n,function(n){return n===t})},j.invoke=function(n,t){var r=o.call(arguments,2),e=j.isFunction(t);return j.map(n,function(n){return(e?t:n[t]).apply(n,r)})},j.pluck=function(n,t){return j.map(n,function(n){return n[t]})},j.where=function(n,t,r){return j.isEmpty(t)?r?void 0:[]:j[r?"find":"filter"](n,function(n){for(var r in t)if(t[r]!==n[r])return!1;return!0})},j.findWhere=function(n,t){return j.where(n,t,!0)},j.max=function(n,t,r){if(!t&&j.isArray(n)&&n[0]===+n[0]&&n.length<65535)return Math.max.apply(Math,n);if(!t&&j.isEmpty(n))return-1/0;var e={computed:-1/0,value:-1/0};return A(n,function(n,u,i){var a=t?t.call(r,n,u,i):n;a>e.computed&&(e={value:n,computed:a})}),e.value},j.min=function(n,t,r){if(!t&&j.isArray(n)&&n[0]===+n[0]&&n.length<65535)return Math.min.apply(Math,n);if(!t&&j.isEmpty(n))return 1/0;var e={computed:1/0,value:1/0};return A(n,function(n,u,i){var a=t?t.call(r,n,u,i):n;ae||r===void 0)return 1;if(e>r||e===void 0)return-1}return n.indexi;){var o=i+a>>>1;r.call(e,n[o])=0})})},j.difference=function(n){var t=c.apply(e,o.call(arguments,1));return j.filter(n,function(n){return!j.contains(t,n)})},j.zip=function(){for(var n=j.max(j.pluck(arguments,"length").concat(0)),t=new Array(n),r=0;n>r;r++)t[r]=j.pluck(arguments,""+r);return t},j.object=function(n,t){if(null==n)return{};for(var r={},e=0,u=n.length;u>e;e++)t?r[n[e]]=t[e]:r[n[e][0]]=n[e][1];return r},j.indexOf=function(n,t,r){if(null==n)return-1;var e=0,u=n.length;if(r){if("number"!=typeof r)return e=j.sortedIndex(n,t),n[e]===t?e:-1;e=0>r?Math.max(0,u+r):r}if(y&&n.indexOf===y)return n.indexOf(t,r);for(;u>e;e++)if(n[e]===t)return e;return-1},j.lastIndexOf=function(n,t,r){if(null==n)return-1;var e=null!=r;if(b&&n.lastIndexOf===b)return e?n.lastIndexOf(t,r):n.lastIndexOf(t);for(var u=e?r:n.length;u--;)if(n[u]===t)return u;return-1},j.range=function(n,t,r){arguments.length<=1&&(t=n||0,n=0),r=arguments[2]||1;for(var e=Math.max(Math.ceil((t-n)/r),0),u=0,i=new Array(e);e>u;)i[u++]=n,n+=r;return i};var M=function(){};j.bind=function(n,t){var r,e;if(w&&n.bind===w)return w.apply(n,o.call(arguments,1));if(!j.isFunction(n))throw new TypeError;return r=o.call(arguments,2),e=function(){if(!(this instanceof e))return n.apply(t,r.concat(o.call(arguments)));M.prototype=n.prototype;var u=new M;M.prototype=null;var i=n.apply(u,r.concat(o.call(arguments)));return Object(i)===i?i:u}},j.partial=function(n){var t=o.call(arguments,1);return function(){return n.apply(this,t.concat(o.call(arguments)))}},j.bindAll=function(n){var t=o.call(arguments,1);if(0===t.length)throw new Error("bindAll must be passed function names");return A(t,function(t){n[t]=j.bind(n[t],n)}),n},j.memoize=function(n,t){var r={};return t||(t=j.identity),function(){var e=t.apply(this,arguments);return j.has(r,e)?r[e]:r[e]=n.apply(this,arguments)}},j.delay=function(n,t){var r=o.call(arguments,2);return setTimeout(function(){return n.apply(null,r)},t)},j.defer=function(n){return j.delay.apply(j,[n,1].concat(o.call(arguments,1)))},j.throttle=function(n,t,r){var e,u,i,a=null,o=0;r||(r={});var c=function(){o=r.leading===!1?0:new Date,a=null,i=n.apply(e,u)};return function(){var l=new Date;o||r.leading!==!1||(o=l);var f=t-(l-o);return e=this,u=arguments,0>=f?(clearTimeout(a),a=null,o=l,i=n.apply(e,u)):a||r.trailing===!1||(a=setTimeout(c,f)),i}},j.debounce=function(n,t,r){var e,u=null;return function(){var i=this,a=arguments,o=function(){u=null,r||(e=n.apply(i,a))},c=r&&!u;return clearTimeout(u),u=setTimeout(o,t),c&&(e=n.apply(i,a)),e}},j.once=function(n){var t,r=!1;return function(){return r?t:(r=!0,t=n.apply(this,arguments),n=null,t)}},j.wrap=function(n,t){return function(){var r=[n];return a.apply(r,arguments),t.apply(this,r)}},j.compose=function(){var n=arguments;return function(){for(var t=arguments,r=n.length-1;r>=0;r--)t=[n[r].apply(this,t)];return t[0]}},j.after=function(n,t){return function(){return--n<1?t.apply(this,arguments):void 0}},j.keys=_||function(n){if(n!==Object(n))throw new TypeError("Invalid object");var t=[];for(var r in n)j.has(n,r)&&t.push(r);return t},j.values=function(n){var t=[];for(var r in n)j.has(n,r)&&t.push(n[r]);return t},j.pairs=function(n){var t=[];for(var r in n)j.has(n,r)&&t.push([r,n[r]]);return t},j.invert=function(n){var t={};for(var r in n)j.has(n,r)&&(t[n[r]]=r);return t},j.functions=j.methods=function(n){var t=[];for(var r in n)j.isFunction(n[r])&&t.push(r);return t.sort()},j.extend=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)n[r]=t[r]}),n},j.pick=function(n){var t={},r=c.apply(e,o.call(arguments,1));return A(r,function(r){r in n&&(t[r]=n[r])}),t},j.omit=function(n){var t={},r=c.apply(e,o.call(arguments,1));for(var u in n)j.contains(r,u)||(t[u]=n[u]);return t},j.defaults=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)n[r]===void 0&&(n[r]=t[r])}),n},j.clone=function(n){return j.isObject(n)?j.isArray(n)?n.slice():j.extend({},n):n},j.tap=function(n,t){return t(n),n};var S=function(n,t,r,e){if(n===t)return 0!==n||1/n==1/t;if(null==n||null==t)return n===t;n instanceof j&&(n=n._wrapped),t instanceof j&&(t=t._wrapped);var u=l.call(n);if(u!=l.call(t))return!1;switch(u){case"[object String]":return n==String(t);case"[object Number]":return n!=+n?t!=+t:0==n?1/n==1/t:n==+t;case"[object Date]":case"[object Boolean]":return+n==+t;case"[object RegExp]":return n.source==t.source&&n.global==t.global&&n.multiline==t.multiline&&n.ignoreCase==t.ignoreCase}if("object"!=typeof n||"object"!=typeof t)return!1;for(var i=r.length;i--;)if(r[i]==n)return e[i]==t;var a=n.constructor,o=t.constructor;if(a!==o&&!(j.isFunction(a)&&a instanceof a&&j.isFunction(o)&&o instanceof o))return!1;r.push(n),e.push(t);var c=0,f=!0;if("[object Array]"==u){if(c=n.length,f=c==t.length)for(;c--&&(f=S(n[c],t[c],r,e)););}else{for(var s in n)if(j.has(n,s)&&(c++,!(f=j.has(t,s)&&S(n[s],t[s],r,e))))break;if(f){for(s in t)if(j.has(t,s)&&!c--)break;f=!c}}return r.pop(),e.pop(),f};j.isEqual=function(n,t){return S(n,t,[],[])},j.isEmpty=function(n){if(null==n)return!0;if(j.isArray(n)||j.isString(n))return 0===n.length;for(var t in n)if(j.has(n,t))return!1;return!0},j.isElement=function(n){return!(!n||1!==n.nodeType)},j.isArray=x||function(n){return"[object Array]"==l.call(n)},j.isObject=function(n){return n===Object(n)},A(["Arguments","Function","String","Number","Date","RegExp"],function(n){j["is"+n]=function(t){return l.call(t)=="[object "+n+"]"}}),j.isArguments(arguments)||(j.isArguments=function(n){return!(!n||!j.has(n,"callee"))}),"function"!=typeof/./&&(j.isFunction=function(n){return"function"==typeof n}),j.isFinite=function(n){return isFinite(n)&&!isNaN(parseFloat(n))},j.isNaN=function(n){return j.isNumber(n)&&n!=+n},j.isBoolean=function(n){return n===!0||n===!1||"[object Boolean]"==l.call(n)},j.isNull=function(n){return null===n},j.isUndefined=function(n){return n===void 0},j.has=function(n,t){return f.call(n,t)},j.noConflict=function(){return n._=t,this},j.identity=function(n){return n},j.times=function(n,t,r){for(var e=Array(Math.max(0,n)),u=0;n>u;u++)e[u]=t.call(r,u);return e},j.random=function(n,t){return null==t&&(t=n,n=0),n+Math.floor(Math.random()*(t-n+1))};var I={escape:{"&":"&","<":"<",">":">",'"':""","'":"'","/":"/"}};I.unescape=j.invert(I.escape);var T={escape:new RegExp("["+j.keys(I.escape).join("")+"]","g"),unescape:new RegExp("("+j.keys(I.unescape).join("|")+")","g")};j.each(["escape","unescape"],function(n){j[n]=function(t){return null==t?"":(""+t).replace(T[n],function(t){return I[n][t]})}}),j.result=function(n,t){if(null==n)return void 0;var r=n[t];return j.isFunction(r)?r.call(n):r},j.mixin=function(n){A(j.functions(n),function(t){var r=j[t]=n[t];j.prototype[t]=function(){var n=[this._wrapped];return a.apply(n,arguments),z.call(this,r.apply(j,n))}})};var N=0;j.uniqueId=function(n){var t=++N+"";return n?n+t:t},j.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var q=/(.)^/,B={"'":"'","\\":"\\","\r":"r","\n":"n"," ":"t","\u2028":"u2028","\u2029":"u2029"},D=/\\|'|\r|\n|\t|\u2028|\u2029/g;j.template=function(n,t,r){var e;r=j.defaults({},r,j.templateSettings);var u=new RegExp([(r.escape||q).source,(r.interpolate||q).source,(r.evaluate||q).source].join("|")+"|$","g"),i=0,a="__p+='";n.replace(u,function(t,r,e,u,o){return a+=n.slice(i,o).replace(D,function(n){return"\\"+B[n]}),r&&(a+="'+\n((__t=("+r+"))==null?'':_.escape(__t))+\n'"),e&&(a+="'+\n((__t=("+e+"))==null?'':__t)+\n'"),u&&(a+="';\n"+u+"\n__p+='"),i=o+t.length,t}),a+="';\n",r.variable||(a="with(obj||{}){\n"+a+"}\n"),a="var __t,__p='',__j=Array.prototype.join,"+"print=function(){__p+=__j.call(arguments,'');};\n"+a+"return __p;\n";try{e=new Function(r.variable||"obj","_",a)}catch(o){throw o.source=a,o}if(t)return e(t,j);var c=function(n){return e.call(this,n,j)};return c.source="function("+(r.variable||"obj")+"){\n"+a+"}",c},j.chain=function(n){return j(n).chain()};var z=function(n){return this._chain?j(n).chain():n};j.mixin(j),A(["pop","push","reverse","shift","sort","splice","unshift"],function(n){var t=e[n];j.prototype[n]=function(){var r=this._wrapped;return t.apply(r,arguments),"shift"!=n&&"splice"!=n||0!==r.length||delete r[0],z.call(this,r)}}),A(["concat","join","slice"],function(n){var t=e[n];j.prototype[n]=function(){return z.call(this,t.apply(this._wrapped,arguments))}}),j.extend(j.prototype,{chain:function(){return this._chain=!0,this},value:function(){return this._wrapped}})}.call(this); +//# sourceMappingURL=underscore-min.map \ No newline at end of file diff --git a/spec/javascripts/BackboneFactorySpec.js b/spec/javascripts/BackboneFactorySpec.js index 3ba09c2..8c43c50 100644 --- a/spec/javascripts/BackboneFactorySpec.js +++ b/spec/javascripts/BackboneFactorySpec.js @@ -1,10 +1,19 @@ +/*global sinon, describe, beforeEach, afterEach, it, expect, BackboneFactory, User, UserWithSchema, Post, PostWithSchema, Comments */ describe("Backbone Factory", function() { + beforeEach(function() { + this.addMatchers({ + toBeInstanceOf: function(expected) { + return this.actual instanceof expected; + } + }); + }); + describe("Defining and using Sequences", function(){ beforeEach(function() { - var emailSequence = BackboneFactory.define_sequence('email', function(n){ - return "person"+n+"@example.com"; + this.emailSequence = BackboneFactory.define_sequence('email', function(n){ + return "person"+n+"@example.com"; }); }); @@ -19,7 +28,7 @@ describe("Backbone Factory", function() { beforeEach(function() { var emailSequence = BackboneFactory.define_sequence('person_email', function(n){ - return "person"+n+"@example.com"; + return "person"+n+"@example.com"; }); var postFactory = BackboneFactory.define('post', Post, function(){ return { @@ -38,19 +47,19 @@ describe("Backbone Factory", function() { this.postObject = BackboneFactory.create('post'); this.userObject = BackboneFactory.create('user'); }); - + it("return an instance of the Backbone Object requested", function() { - expect(this.postObject instanceof Post).toBeTruthy(); - expect(this.userObject instanceof User).toBeTruthy(); + expect(this.postObject).toBeInstanceOf(Post); + expect(this.userObject).toBeInstanceOf(User); }); - + // Not sure if this test is needed. But what the hell! it("should preserve the defaults if not overriden", function() { expect(this.postObject.get('title')).toBe('Default Title'); }); - + it("should use the defaults supplied when creating objects", function() { expect(this.userObject.get('name')).toBe('Backbone User'); @@ -63,8 +72,8 @@ describe("Backbone Factory", function() { }); it("should work if other factories are passed", function(){ - expect(this.postObject.get('author') instanceof User).toBeTruthy(); - }) + expect(this.postObject.get('author')).toBeInstanceOf(User); + }); it("should override defaults if arguments are passed on creation", function(){ var userWithEmail = BackboneFactory.create('user', function(){ @@ -84,13 +93,26 @@ describe("Backbone Factory", function() { var secondID = BackboneFactory.create('user').id; expect(secondID).toBe(firstID + 1); }); - + + + describe("create collection", function() { + + it("should create models with back-reference to collection", function() { + BackboneFactory.define('comment', Comment); + BackboneFactory.define_collection('comments', Comments, 2); + var collection = BackboneFactory.create_collection('comments'); + expect(collection.at(0).collection).toBe(collection); + }); + + }); + + describe("Error Messages", function() { it("should throw an error if factory_name is not proper", function() { expect(function(){BackboneFactory.define('wrong name', Post)}).toThrow("Factory name should not contain spaces or other funky characters"); }); - + it("should not throw an error if factory_name has a hyphen", function() { expect(function(){BackboneFactory.define('okay-name', Post)}).not.toThrow(); }); @@ -102,10 +124,142 @@ describe("Backbone Factory", function() { it("should throw an error if you try to use an undefined sequence", function() { expect(function(){BackboneFactory.next('undefined_sequence')}).toThrow("Sequence with name undefined_sequence does not exist"); }); - - }); - - }); - -}); + }); + + }); + + describe("Defining and using Factories with Schema", function() { + + beforeEach(function() { + BackboneFactory.define_sequence('person_email', function(n){ + return "person"+n+"@example.com"; + }); + BackboneFactory.define_collection('comments', Comments, 3); + BackboneFactory.define('post_with_schema', PostWithSchema); + BackboneFactory.define('user_with_schema', UserWithSchema, function() { + return { + email: BackboneFactory.next('person_email') + }; + }); + BackboneFactory.define('comment', Comment ); + }); + + + it("should create model using schema if present", function() { + var user = BackboneFactory.create('user_with_schema'); + expect(user.get('name')).toEqual('Backbone User'); + }); + + it("should get email using sequence", function() { + var user = BackboneFactory.create('user_with_schema'); + expect(user.get('email')).toEqual('person1@example.com'); + }); + + it("defaults option should override default from schema", function() { + var post = BackboneFactory.create('post_with_schema'); + expect(post.get('title')).toEqual('Default Title'); + }); + + it("should fallback to schema.default", function() { + var post = BackboneFactory.create('post_with_schema'); + expect(post.get('body')).toEqual('Default body'); + }); + + it("should fallback to schema.default", function() { + var post = BackboneFactory.create('post_with_schema'); + expect(post.get('body')).toEqual('Default body'); + }); + + describe("Related Model", function(){ + it("should be created from schema if exists", function() { + var post = BackboneFactory.create('post_with_schema'); + expect(post.get('author')).toBeInstanceOf(UserWithSchema); + }); + + it("should be created from schema even the related model has no schema", function() { + var post = BackboneFactory.create('post_with_schema'); + expect(post.get('author_without_schema')).toBeInstanceOf(User); + }); + + it("should work even when there are multiple factories for the same constructor", function(){ + BackboneFactory.define('another_user_with_schema', UserWithSchema); + var post = BackboneFactory.create('post_with_schema'); + expect(post.get('author')).toBeInstanceOf(UserWithSchema); + }); + + it("should be created even if there is no factory", function() { + var CommentCopy = Comment.extend({ + schema: _.extend({}, Comment.prototype.schema, { + author: { + type: 'related', + _constructor: Backbone.Model + } + }) + }); + BackboneFactory.define('comment_copy', CommentCopy); + var comment = BackboneFactory.create('comment_copy'); + expect(comment.get('author')).toBeInstanceOf(Backbone.Model); + expect(comment.get('author')).not.toBeInstanceOf(User); + }); + + }); + + describe("Related Collection", function() { + + it("should be created from schema", function() { + var post = BackboneFactory.create('post_with_schema'); + expect(post.get('comments')).toBeInstanceOf(Comments); + }); + + it("should be of default size if size not given", function() { + var post = BackboneFactory.create('post_with_schema'); + expect(post.get('comments').size()).toEqual(3); + }); + + it("should be of default size if size is not valid number", function() { + var post = BackboneFactory.create('post_with_schema', function(){ + return { comments: BackboneFactory.create_collection('comments', null) }; + }); + expect(post.get('comments').size()).toEqual(3); + }); + + it("should be of given size", function() { + var post = BackboneFactory.create('post_with_schema', function(){ + return { comments: BackboneFactory.create_collection('comments', 4) }; + }); + expect(post.get('comments').size()).toEqual(4); + }); + + it("should contain models with given attributes", function() { + BackboneFactory.define_collection('comments', Comments, 3); + var user = BackboneFactory.create('user_with_schema'); + var post = BackboneFactory.create('post_with_schema', function(){ + return { comments: BackboneFactory.create_collection('comments', null, function() { + return { author: user }; + }) }; + }); + expect(_.every(post.get('comments').pluck('author'), function(val) { + return val === user; + })).toBeTruthy(); + }); + + it("should be created even if there is no factory", function() { + var PostCopy = PostWithSchema; + PostCopy.prototype.schema = _.extend(PostWithSchema.prototype.schema, { + comments: { + type: 'related', + _constructor: Backbone.Collection + } + }); + BackboneFactory.define('post', PostCopy); + var post = BackboneFactory.create('post'); + expect(post.get('comments')).toBeInstanceOf(Backbone.Collection); + expect(post.get('comments')).not.toBeInstanceOf(Comments); + }); + + }); + + }); + +}); diff --git a/testem.json b/testem.json new file mode 100644 index 0000000..8ad4702 --- /dev/null +++ b/testem.json @@ -0,0 +1,11 @@ +{ + "framework": "jasmine", + "launch_in_dev": ["PhantomJS"], + "src_files": [ + "public/javascripts/underscore-min.js", + "public/javascripts/backbone-min.js", + "public/javascripts/test-setup.js", + "public/javascripts/backbone-factory.js", + "spec/javascripts/BackboneFactorySpec.js" + ] +}