UNPKG

transactional

Version:

Reactive objects with transactional updates and automatic serialization

376 lines (296 loc) 12.4 kB
var Nested = require( '../nestedtypes' ), chai = require( 'chai' ), expect = chai.expect, sinon = require( 'sinon' ), sinonChai = require( 'sinon-chai' ); chai.use( sinonChai ); describe( 'Type specs', function(){ describe( 'Constructor type spec', function(){ var Ctor = Nested.Class.extend({ a : 1, initialize : function( a ){ a === undefined || ( this.a = a ); }, toJSON : function(){ return this.a; } }); var A = Nested.Model.extend({ defaults : { a : Ctor } }); describe( 'Model creation', function(){ it( 'automatically creates new object', function(){ var m = new A(); expect( m.a ).to.be.instanceOf( Ctor ); expect( m.a.a ).to.eql( 1 ); }); it( 'pass value to the constructor on creation ', function(){ var m = new A({ a : 3 }); expect( m.a ).to.be.instanceOf( Ctor ); expect( m.a.a ).to.eql( 3 ); }); it( 'may have default value', function(){ var A = Nested.Model.extend({ defaults : { a : Ctor.value( 3 ) } }); var m = new A(); expect( m.a ).to.be.instanceOf( Ctor ); expect( m.a.a ).to.eql( 3 ); }); it( 'may have default value of null', function(){ var A = Nested.Model.extend({ defaults : { a : Ctor.value( null ) } }); var m = new A(); expect( m.a ).to.be.null; }); }); describe( 'Attribute assignment', function(){ it( 'replace value if assigned with defined type', function(){ var m = new A(); m.a = new Ctor( 5 ); expect( m.a ).to.be.instanceOf( Ctor ); expect( m.a.a ).to.eql( 5 ); }); it( 'convert value to defined type on assignment', function(){ var m = new A(); m.a = 5; expect( m.a ).to.be.instanceOf( Ctor ); expect( m.a.a ).to.eql( 5 ); }); it( 'may set attribute with null', function(){ var m = new A(); m.a = null; expect( m.a ).to.be.null; }); }); describe( 'Serialization and parsing', function(){ it( 'serialize attribute to JSON', function(){ var m = new A(), json = m.toJSON(); expect( json.a ).to.eql( 1 ); }); it( 'set attribute on model set', function(){ var m = new A(); m.set({ a : 5 }); expect( m.a ).to.be.instanceOf( Ctor ); expect( m.a.a ).to.eql( 5 ); }); }); }); describe( 'Array type', function(){ var M = Nested.Model.extend({ defaults : { arr : Array } }); it( 'creates an empty array by default', function(){ var m = new M(); expect( m.arr ).to.be.instanceOf( Array ); expect( m.arr.length ).to.be.equal( 0 ); }); it( 'wrap non-array value in array on assignment', function(){ var m = new M(); m.arr = 1; expect( m.arr ).to.be.instanceOf( Array ); expect( m.arr[ 0 ] ).to.be.equal( 1 ); }); }); describe( 'Primitive types (Number, Integer, Boolean, String)', function(){ var A = Nested.Model.extend({ defaults : { num : Number, str : String, bool : Boolean, int : Integer } }); it( 'initialized with primitive types', function(){ var m = new A(); expect( m.num ).to.be.a( 'number' ).and.equal( 0 ); expect( m.int ).to.be.a( 'number' ).and.equal( 0 ); expect( m.str ).to.be.a( 'string' ).and.equal( "" ); expect( m.bool ).to.be.a( 'boolean' ).and.equal( false ); }); it( 'converted to defined types on assignments', function(){ var m = new A(); m.num = "25.5"; expect( m.num ).to.be.a( 'number' ).and.equal( 25.5 ); m.str = 32; expect( m.str ).to.be.a( 'string' ).and.equal( "32" ); m.bool = "5"; expect( m.bool ).to.be.a( 'boolean' ).and.equal( true ); m.int = 25.7; expect( m.int ).to.be.a( 'number' ).and.equal( 26 ); m.int = "25.7"; expect( m.int ).to.be.a( 'number' ).and.equal( 26 ); }); it( 'can be set with null', function(){ var m = new A(); m.num = null; expect( m.num ).to.be.null; m.str = null; expect( m.str ).to.be.null; m.bool = null; expect( m.bool ).to.be.null; m.int = null; expect( m.int ).to.be.null; }); it( 'inferred from default values (except Integer)', function(){ var A = Nested.Model.extend({ defaults : { num : 1, str : 'str', bool : true } }); var m = new A(); expect( m.num ).to.be.a( 'number' ).and.equal( 1 ); expect( m.str ).to.be.a( 'string' ).and.equal( "str" ); expect( m.bool ).to.be.a( 'boolean' ).and.equal( true ); }); }); describe( 'Date type', function(){ var user, User = Nested.Model.extend({ attributes:{ created: Date, name: String, loginCount: Integer } }); before( function(){ user = new User(); }); it( 'create new Date object on construction', function(){ expect( user.created ).to.be.instanceOf( Date ); }); it( 'parse ISO dates in all browsers on assignment', function(){ // parse Date from string user.created = "2012-12-12T10:00"; expect( user.created ).to.be.instanceof( Date ); expect( user.created.toISOString() ).to.be.eql( '2012-12-12T10:00:00.000Z' ); }); it( 'parse integer time stamps on assignment', function(){ // parse Date from timestamp user.created = 1234567890123; expect( user.created ).to.be.instanceof( Date ); expect( user.created.toISOString() ).to.be.eql( '2009-02-13T23:31:30.123Z' ); }); it( 'parse MS time stamps on assignment', function(){ user.created = "/Date(1234567890123)/"; expect( user.created ).to.be.instanceof( Date ); expect( user.created.toISOString() ).to.be.eql( '2009-02-13T23:31:30.123Z' ); }); it( 'is serialized to ISO date', function(){ var json = user.toJSON(); expect( json.created ).to.be.eql( '2009-02-13T23:31:30.123Z' ); } ); }); describe( 'Attribute options', function(){ describe( 'get hook', function(){ var A = Nested.Model.extend({ defaults : { a : Number.has .get( function( value ){ return value * 2; }) } }); var m = new A(); m.a = 1; it( 'may modify returned attribute\'s value', function(){ expect( m.a ).to.be.equal( 2 ); }); it( 'is called from model.get', function(){ expect( m.get( 'a' ) ).to.be.equal( 2 ); }); }); describe( 'set hook', function(){ var A = Nested.Model.extend({ defaults : { a : Number.value( 33 ) .set( function( value, options ){ expect( value ).to.be.a( 'number' ); if( value !== 0 ){ return value * 2; } }), b : Number.has.watcher( 'watcher' ) } }); it( 'may modify value assigned to attribute\'s', function(){ var m = new A(); m.a = 1; expect( m.a ).to.be.equal( 2 ); }); it( 'is called from model.set', function(){ var m = new A(); m.set( 'a', 1 ); expect( m.a ).to.be.equal( 2 ); m.set( { a : 3 } ); expect( m.a ).to.be.equal( 6 ); }); it( 'is called after type cast', function(){ var m = new A(); m.a = "1"; expect( m.a ).to.be.equal( 2 ); }); it( 'may prevent attribute\'s assignment', function(){ var m = new A(); m.set( 'a', 0 ); expect( m.a ).to.be.equal( 66 ); }); it( 'may prevent attribute\'s assignment', function(){ var m = new A(); m.watcher = sinon.spy(); m.b = 77; expect( m.watcher ).to.be.calledOnce; expect( m.watcher ).to.be.calledWith( 77 ); }); }); describe( 'toJSON hook', function(){ it( 'override attribute\'s toJSON', function(){ var A = Nested.Model.extend({ defaults : { a : Date.has .toJSON( function( date ){ return date.getTime(); }) } }); var m = new A(), json = m.toJSON(); expect( json.a ).to.be.a( 'number' ); }); it( 'can prevent attribute from serialization', function(){ var A = Nested.Model.extend({ defaults : { a : Date.has.toJSON( false ), b : true } }); var m = new A(), json = m.toJSON(); expect( json.a ).to.not.exist; expect( json.b ).to.be.true; }); }); it( 'can define untyped attribute', function(){ var A = Nested.Model.extend({ defaults : { a : Nested.value( 1 ) } }); var m = new A(); expect( m.a ).to.equal( 1 ); m.a = "1"; expect( m.a ).to.equal( "1" ); }); }); });