transactional
Version:
Reactive objects with transactional updates and automatic serialization
290 lines (247 loc) • 9 kB
text/typescript
import { define } from '../src/class'
import { Record } from '../src/record/'
import { expect } from 'chai'
describe( 'Record', () =>{
it( "can be instantiated", ()=>{
new Record();
} );
describe( 'Subclass', ()=>{
it( "attaches properties to prototype", ()=>{
( {
a : 'a'
} )
class M extends Record {}
expect( M.prototype.a ).to.eql( 'a' );
} );
} );
describe( "Attribute spec", () =>{
describe( '...as constructors', () =>{
( {
attributes : {
s : String,
n : Number,
b : Boolean,
o : Object,
a : Array,
d : Date
}
} )
class M extends Record {
}
it( "invokes constructor to create defaults", () =>{
const m = new M();
expect( m.s ).to.equal( '' );
expect( m.n ).to.equal( 0 );
expect( m.b ).to.equal( false );
expect( m.o ).to.eql( {} );
expect( m.a ).to.eql( [] );
expect( m.d ).to.be.instanceof( Date );
} );
it( "convert values to defined type in 'new'", () =>{
const m = new M( {
s : 55,
n : "1",
b : 'not bool',
o : "not an object",
a : "not an array",
d : 678678678
} );
expect( m.s ).to.equal( '55' );
expect( m.n ).to.equal( 1 );
expect( m.b ).to.equal( true );
expect( m.o ).to.eql( {} );
expect( m.a ).to.eql( [] );
expect( m.d ).to.be.instanceof( Date );
} );
it( "convert values to defined types on assignment", () =>{
const m = new M();
m.s = 55;
m.n = "1";
m.b = 'not bool';
m.o = "not an object";
m.a = "not an array";
m.d = 678678678;
expect( m.s ).to.equal( '55' );
expect( m.n ).to.equal( 1 );
expect( m.b ).to.equal( true );
expect( m.o ).to.eql( {} );
expect( m.a ).to.eql( [] );
expect( m.d ).to.be.instanceof( Date );
} );
it( "convert values to defined types on set", () =>{
const m = new M();
m.set({
s : 55,
n : "1",
b : 'not bool',
o : "not an object",
a : "not an array",
d : 678678678
});
expect( m.s ).to.equal( '55' );
expect( m.n ).to.equal( 1 );
expect( m.b ).to.equal( true );
expect( m.o ).to.eql( {} );
expect( m.a ).to.eql( [] );
expect( m.d ).to.be.instanceof( Date );
} );
} );
describe( '...as default values', () =>{
( {
attributes : {
s : 'b',
n : 1,
b : true,
o : {},
a : [],
d : new Date()
}
} )
class M extends Record {
}
it( "accepts values as type spec", () =>{
const m = new M();
expect( m.s ).to.equal( 'b' );
expect( m.n ).to.equal( 1 );
expect( m.b ).to.equal( true );
expect( m.o ).to.not.equal( {} );
expect( m.o ).to.eql( {} );
expect( m.a ).to.not.equal( [] );
expect( m.a ).to.eql( [] );
expect( m.d ).to.be.instanceof( Date );
} );
it( "infers types from values", () =>{
const m = new M(),
{ _attributes } = m;
expect( _attributes.s.type ).to.equal( String );
expect( _attributes.n.type ).to.equal( Number );
expect( _attributes.b.type ).to.equal( Boolean );
expect( _attributes.o.type ).to.equal( Object );
expect( _attributes.a.type ).to.equal( Array );
expect( _attributes.d.type ).to.equal( Date );
} );
} );
describe( '...as constructors with values', () =>{
it( "converts default values to defined types", () =>{
( {
attributes : {
s : String.value( 55 ),
n : Number.value( "1" ),
b : Boolean.value( "some" ),
o : Object.value( "not an object" ),
a : Array.value( "not an array" ),
d : Date.value( 22222 )
}
} )
class M extends Record {}
const m = new M();
expect( m.s ).to.equal( '55' );
expect( m.n ).to.equal( 1 );
expect( m.b ).to.equal( true );
expect( m.o ).to.eql( {} );
expect( m.a ).to.eql( [] );
expect( m.d ).to.be.instanceof( Date );
} );
it( "accepts null as default value", () =>{
( {
attributes : {
s : String.value( null ),
n : Number.value( null ),
b : Boolean.value( null ),
o : Object.value( null ),
a : Array.value( null ),
d : Date.value( null )
}
} )
class M extends Record {}
const m = new M();
expect( m.s ).to.equal( null );
expect( m.n ).to.equal( null);
expect( m.b ).to.equal( null );
expect( m.o ).to.eql( null );
expect( m.a ).to.eql( null );
expect( m.d ).to.eql( null );
} );
} );
});
describe( "Record's collection", () =>{
it( "is defined in the base Record", ()=>{
expect( Record.Collection ).to.be.a( 'function' );
expect( Record.Collection.prototype.Record ).to.eql( Record );
} );
it( "is created on Record's extension", () =>{
class M extends Record {
}
const { prototype } = M.Collection;
expect( prototype ).to.be.instanceof( Record.Collection );
expect( prototype.Record ).to.eql( M );
} );
it( "takes properties from .collection", () =>{
( {
collection : {
a : 'a'
}
} )
class M extends Record {
}
expect( M.Collection.prototype.a ).to.eql( 'a' );
} );
it( "can be defined separately", () =>{
( {
a : 'a'
} )
class C extends Collection {
}
( {
collection : C
} )
class M extends Record {
}
expect( M.Collection ).to.eql( C );
const { prototype } = M.Collection;
expect( prototype ).to.be.instanceof( Record.Collection );
expect( prototype.a ).to.eql( 'a' );
expect( prototype.Record ).to.eql( M );
} );
} );
describe( 'Record pre-definition', ()=>{
class M1 extends Record {
}
class M2 extends Record {}
M2.define();
it( 'predefine collection types', ()=>{
expect( M1.Collection ).to.be.func;
expect( M2.Collection ).to.be.func;
} );
it( "can't be instantiated", ()=>{
new M1(); //should throw
} );
it( 'support forward declaration', () =>{
class M extends Record {
}
expect( M.Collection ).to.be.a( 'function' );
expect( M.Collection.prototype.Record ).to.eql( M );
M.define( {
a : 'a'
} );
expect( M.prototype.a ).to.eql( 'a' );
} );
it( 'can be defined', ()=>{
M1.define( {
a : 'first',
collection : {
b : 'second'
}
} );
M2.define( {
a : 'first'
} );
M2.Collection.define( {
b : 'second'
} );
} );
} );
} );