useful-class
Version:
useful Classical inheritance scaffolding, including singletons and easy to use mixin functionality
601 lines (492 loc) • 18.2 kB
JavaScript
suite( 'muigui/useful-Class', function() {
var mod = {}, // dummy module to assign classes to
// class definitions
Class_01 = Class( {
constructor : function LoremIpsum( greeting ) {
this.greeting = greeting;
this.setNum( 10 );
},
getNum : function() { return this.num; },
setNum : function( num ) {
this.num = num;
return this.getNum();
},
statics : {
foo : 'bar',
bam : function() { return 'bam'; }
}
} ),
Class_02 = Class.define( 'path.to.Class_02', {
constructor : function( greeting ) {
this.parent( 'class_02: ' + greeting );
},
extend : Class_01,
module : mod,
getNum : function() {
return this.parent();
},
statics : {
foo : 'barfly'
}
} ),
Class_03 = Class.define( 'Class_03', {
constructor : function Class_03( greeting ) {
this.parent( 'class_03: ' + greeting );
},
extend : 'path.to.Class_02',
module : mod,
statics : {
bam : function() { return 'bambam'; }
}
} ),
Class_04 = Class_03.extend( {
constructor : function Class_04( greeting ) {
this.parent( 'class_04: ' + greeting );
},
getNum : function() {
return this.parent();
},
statics : {
foo : 'barflyonthewall',
bam : function() { return 'bambambam'; }
}
} ),
// instances
instance_01 = new Class_01( 'hello world!' ),
instance_02 = Class.new( 'path.to.Class_02', 'hello world!' ),
instance_03 = Class_03.new.call( this, 'hello world!' ),
instance_04 = Class_04.new.apply( this, ['hello world!'] ),
// base namespace for test classes
path = mod;
Class.define( 'path.to.Singleton_01', {
constructor : function() { this.parent( 'singleton_01: hello world!' ); },
extend : Class_04,
module : mod,
singleton : true,
getNum : function() { return this.parent(); }
} );
suite( 'core functionality', function() {
test( '<static> Class.is', function( done ) {
expect( Class.is( instance_01, Class_01 ) ).to.be.true;
expect( Class.is( instance_01, Object ) ).to.be.true;
expect( Class.is( instance_02, Class_02 ) ).to.be.true;
expect( Class.is( instance_02, Class_01 ) ).to.be.true;
expect( Class.is( instance_02, Object ) ).to.be.true;
expect( Class.is( instance_03, Class_03 ) ).to.be.true;
expect( Class.is( instance_03, Class_02 ) ).to.be.true;
expect( Class.is( instance_03, Class_01 ) ).to.be.true;
expect( Class.is( instance_03, Object ) ).to.be.true;
expect( Class.is( instance_04, Class_04 ) ).to.be.true;
expect( Class.is( instance_04, Class_03 ) ).to.be.true;
expect( Class.is( instance_04, Class_02 ) ).to.be.true;
expect( Class.is( instance_04, Class_01 ) ).to.be.true;
expect( Class.is( instance_04, Object ) ).to.be.true;
expect( Class.is( path.to.Singleton_01, path.to.Singleton_01.constructor ) ).to.be.true;
expect( Class.is( path.to.Singleton_01, Class_04 ) ).to.be.true;
expect( Class.is( path.to.Singleton_01, Class_03 ) ).to.be.true;
expect( Class.is( path.to.Singleton_01, Class_02 ) ).to.be.true;
expect( Class.is( path.to.Singleton_01, Class_01 ) ).to.be.true;
expect( Class.is( path.to.Singleton_01, Object ) ).to.be.true;
done();
} );
test( 'instantiating with the new operator', function( done ) {
var f, b, z, w;
expect( ( f = new Class_01( 'hello world!' ) ) instanceof Class_01 ).to.be.true;
expect( f.greeting ).to.eql( 'hello world!' );
expect( ( b = new Class_02( 'hello world!' ) ) instanceof Class.get( 'path.to.Class_02' ) ).to.be.true;
expect( b instanceof Class_01 ).to.be.true;
expect( b.greeting ).to.eql( 'class_02: hello world!' );
expect( ( z = new Class_03( 'hello world!' ) ) instanceof Class.get( 'Class_03' ) ).to.be.true;
expect( z instanceof Class_02 ).to.be.true;
expect( z instanceof Class_01 ).to.be.true;
expect( z.greeting ).to.eql( 'class_02: class_03: hello world!' );
expect( ( w = new Class_04( 'hello world!' ) ) instanceof Class_04 ).to.be.true;
expect( w instanceof Class_03 ).to.be.true;
expect( w instanceof Class_02 ).to.be.true;
expect( w instanceof Class_01 ).to.be.true;
expect( w.greeting ).to.eql( 'class_02: class_03: class_04: hello world!' );
expect( path.to.Singleton_01 instanceof path.to.Singleton_01.constructor ).to.be.true;
expect( path.to.Singleton_01 instanceof Class_04 ).to.be.true;
expect( path.to.Singleton_01 instanceof Class_03 ).to.be.true;
expect( path.to.Singleton_01 instanceof Class_02 ).to.be.true;
expect( path.to.Singleton_01 instanceof Class_01 ).to.be.true;
done();
} );
test( 'instantiating without the new operator', function( done ) {
var f, b, z, w;
expect( ( f = Class_01( 'hello world!' ) ) instanceof Class_01 ).to.be.true;
expect( f.greeting ).to.eql( 'hello world!' );
expect( ( b = Class_02( 'hello world!' ) ) instanceof Class_02 ).to.be.true;
expect( b.greeting ).to.eql( 'class_02: hello world!' );
expect( ( z = Class_03( 'hello world!' ) ) instanceof Class_03 ).to.be.true;
expect( z.greeting ).to.eql( 'class_02: class_03: hello world!' );
expect( ( w = Class_04( 'hello world!' ) ) instanceof Class_04 ).to.be.true;
expect( w.greeting ).to.eql( 'class_02: class_03: class_04: hello world!' );
done();
} );
test( 'instantiating a Class with its `create`/`new` factory methods', function( done ) {
var f, b, z, w;
expect( ( f = Class_01.create( 'hello world!' ) ) instanceof Class_01 ).to.be.true;
expect( f.greeting ).to.eql( 'hello world!' );
expect( ( b = Class_02.new( 'hello world!' ) ) instanceof Class_02 ).to.be.true;
expect( b.greeting ).to.eql( 'class_02: hello world!' );
expect( ( z = Class_03.create( 'hello world!' ) ) instanceof Class_03 ).to.be.true;
expect( z.greeting ).to.eql( 'class_02: class_03: hello world!' );
expect( ( w = Class_04.new( 'hello world!' ) ) instanceof Class_04 ).to.be.true;
expect( w.greeting ).to.eql( 'class_02: class_03: class_04: hello world!' );
done();
} );
test( 'instantiating a Class with the top level Class.new factory method', function( done ) {
var f, b, z, w;
expect( ( b = Class.new( 'path.to.Class_02', 'hello world!' ) ) instanceof Class_02 ).to.be.true;
expect( b.greeting ).to.eql( 'class_02: hello world!' );
expect( ( z = Class.new( 'Class_03', 'hello world!' ) ) instanceof Class_03 ).to.be.true;
expect( z.greeting ).to.eql( 'class_02: class_03: hello world!' );
expect( ( w = Class.new( Class_04, 'hello world!' ) ) instanceof Class_04 ).to.be.true;
expect( w.greeting ).to.eql( 'class_02: class_03: class_04: hello world!' );
done();
} );
test( 'inheritance', function( done ) {
instance_01.setNum( 10 ); instance_02.setNum( 10 );
instance_03.setNum( 10 ); instance_04.setNum( 10 );
path.to.Singleton_01.setNum( 10 );
expect( instance_01.getNum() ).to.eql( 10 );
expect( instance_01.setNum( 100 ) ).to.eql( 100 );
expect( instance_01.getNum() ).to.eql( 100 );
expect( instance_02.getNum() ).to.eql( 10 );
expect( instance_02.setNum( 200 ) ).to.eql( 200 );
expect( instance_01.getNum() ).to.eql( 100 );
expect( instance_03.getNum() ).to.eql( 10 );
expect( instance_03.setNum( 400 ) ).to.eql( 400 );
expect( instance_01.getNum() ).to.eql( 100 );
expect( instance_02.getNum() ).to.eql( 200 );
expect( instance_04.getNum() ).to.eql( 10 );
expect( instance_04.setNum( 800 ) ).to.eql( 800 );
expect( instance_01.getNum() ).to.eql( 100 );
expect( instance_02.getNum() ).to.eql( 200 );
expect( instance_03.getNum() ).to.eql( 400 );
expect( path.to.Singleton_01.getNum() ).to.eql( 10 );
expect( path.to.Singleton_01.setNum( 1000 ) ).to.eql( 1000 );
expect( instance_01.getNum() ).to.eql( 100 );
expect( instance_02.getNum() ).to.eql( 200 );
expect( instance_03.getNum() ).to.eql( 400 );
expect( instance_04.getNum() ).to.eql( 800 );
done();
} );
test( 'singletons', function( done ) {
expect( new path.to.Singleton_01.constructor() ).to.equal( path.to.Singleton_01 );
expect( path.to.Singleton_01.constructor() ).to.equal( path.to.Singleton_01 );
expect( path.to.Singleton_01.constructor.create() ).to.equal( path.to.Singleton_01 );
expect( Class.new( 'path.to.Singleton_01', 1, 2, 3 ) ).to.equal( path.to.Singleton_01 );
done();
} );
test( 'static properties and methods', function( done ) {
expect( Class_01.foo ).to.equal( 'bar' );
expect( Class_02.foo ).to.equal( 'barfly' );
expect( Class_03.foo ).to.equal( 'barfly' );
expect( Class_04.foo ).to.equal( 'barflyonthewall' );
expect( path.to.Singleton_01.constructor.foo ).to.equal( 'barflyonthewall' );
expect( Class_01.bam() ).to.equal( 'bam' );
expect( Class_02.bam() ).to.equal( 'bam' );
expect( Class_03.bam() ).to.equal( 'bambam' );
expect( Class_04.bam() ).to.equal( 'bambambam' );
expect( path.to.Singleton_01.constructor.bam() ).to.equal( 'bambambam' );
done();
} );
test( 'accessors', function( done ) {
var Accessors = Class( {
accessors : {
foo : {
get : function() {
return this._foo;
},
set : function( value ) {
return this._foo = value;
}
},
bar : {
get : function() {
return this.foo;
},
set : function( value ) {
return this.foo;
}
},
bam : {
get : function() {
return 'BAM!!!';
}
},
baz : {
set : function( value ) {
return this.foo = value;
}
}
},
_foo : null
} ),
accessors = new Accessors();
expect( accessors._foo ).to.equal( null );
expect( accessors.foo ).to.equal( null );
accessors.foo = 'foo';
expect( accessors.foo ).to.equal( 'foo' );
expect( accessors._foo ).to.equal( 'foo' );
expect( accessors.bar ).to.equal( 'foo' );
expect( accessors.bar = 'bar' ).to.equal( 'bar' );
expect( accessors.bar ).to.equal( 'foo' );
expect( accessors._foo ).to.equal( 'foo' );
expect( accessors.bam ).to.equal( 'BAM!!!' );
expect( accessors.bam = 'BOOH!!!' ).to.equal( 'BOOH!!!' );
expect( accessors.bam ).to.equal( 'BAM!!!' );
expect( accessors.baz ).to.equal( undefined );
accessors.baz = 'bazooka tooth';
expect( accessors.baz ).to.equal( undefined );
expect( accessors._foo ).to.equal( 'bazooka tooth' );
done();
} );
} );
suite( 'pre/post processing', function() {
test( 'executing functions after a Class is created', function( done ) {
var after_define_01 = 0, after_define_02 = 0, after_define_02a = 0;
Class.define( 'PostProcessingTest_01', {
afterdefine : function() {
++after_define_01;
},
constructor : function PostProcessingTest_01() {
this.parent( arguments );
},
module : mod
} );
expect( after_define_01 ).to.be.equal( 1 );
Class.define( 'PostProcessingTest_02', {
afterdefine : function() {
++after_define_02;
},
extend : mod.PostProcessingTest_01,
module : mod
} );
expect( after_define_01 ).to.be.equal( 2 );
expect( after_define_02 ).to.be.equal( 1 );
Class.define( 'PostProcessingTest_02a', {
afterdefine : function() {
++after_define_02a;
},
extend : mod.PostProcessingTest_02,
module : mod
} );
expect( after_define_01 ).to.be.equal( 3 );
expect( after_define_02 ).to.be.equal( 2 );
expect( after_define_02a ).to.be.equal( 1 );
done();
} );
test( 'executing functions before a Class is instantiated', function( done ) {
var before_instance_01 = 0, before_instance_02 = 0, before_instance_02a = 0;
Class.define( 'PreProcessingTest_03', {
beforeinstance : function( Class, instance, args ) {
expect( instance ).to.be.an.instanceof( Class );
expect( args[0] ).to.eql( [1,2,3] );
++before_instance_01;
},
constructor : function PreProcessingTest_03() {
this.parent( arguments );
},
module : mod
} );
Class.new( 'PreProcessingTest_03', [1, 2, 3] );
expect( before_instance_01 ).to.be.equal( 1 );
Class.define( 'PreProcessingTest_04', {
beforeinstance : function( Class, instance, args ) {
expect( instance ).to.be.an.instanceof( Class );
expect( args[0] ).to.eql( [1,2,3] );
++before_instance_02;
},
extend : mod.PreProcessingTest_03,
module : mod
} );
Class.new( 'PreProcessingTest_04', [1, 2, 3] );
expect( before_instance_01 ).to.be.equal( 2 );
expect( before_instance_02 ).to.be.equal( 1 );
Class.define( 'PreProcessingTest_04a', {
beforeinstance : function( Class, instance, args ) {
expect( instance ).to.be.an.instanceof( Class );
expect( args[0] ).to.eql( [1,2,3] );
++before_instance_02a;
},
extend : mod.PreProcessingTest_04,
module : mod
} );
Class.new( 'PreProcessingTest_04a', [1, 2, 3] );
expect( before_instance_01 ).to.be.equal( 3 );
expect( before_instance_02 ).to.be.equal( 2 );
expect( before_instance_02a ).to.be.equal( 1 );
done();
} );
} );
suite( 'overriding methods', function() {
test( 'overriding a Class\' methods', function( done ) {
var called_getNum = false,
called_setNum = false,
instance;
Class_02.override( 'getNum', function() {
called_getNum = true;
return this.original();
} );
Class_02.override( {
setNum : function( num ) {
called_setNum = true;
num += 100;
return this.original( arguments );
}
} );
instance = new Class_02( 'hello' );
expect( called_getNum ).to.be.true;
expect( called_setNum ).to.be.true;
expect( instance.num ).to.equal( 110 );
called_getNum = false;
expect( instance.getNum() ).to.equal( 110 );
expect( called_getNum ).to.be.true;
expect( instance.getNum() ).to.equal( instance.num );
called_setNum = false;
expect( instance.setNum( 100 ) ).to.equal( 200 );
expect( called_setNum ).to.be.true;
expect( instance.num ).to.equal( 200 );
done();
} );
test( 'overriding a Class instance\'s methods', function( done ) {
var called_getNum = false,
called_setNum = false,
instance = new Class_01( 'hello' );
instance.__override__( 'getNum', function() {
called_getNum = true;
return this.original();
} );
instance.__override__( 'setNum', function( num ) {
called_setNum = true;
num += 100;
return this.original( arguments );
} );
instance.getNum();
expect( called_getNum ).to.be.true;
instance.setNum( 10 );
expect( called_setNum ).to.be.true;
expect( instance.num ).to.equal( 110 );
called_getNum = false;
expect( instance.getNum() ).to.equal( 110 );
expect( called_getNum ).to.be.true;
expect( instance.getNum() ).to.equal( instance.num );
called_setNum = false;
expect( instance.setNum( 100 ) ).to.equal( 200 );
expect( called_setNum ).to.be.true;
expect( instance.num ).to.equal( 200 );
called_getNum = false;
called_setNum = false;
instance = new Class_01( 'howdy' );
instance.getNum();
expect( called_getNum ).to.be.false;
expect( called_setNum ).to.be.false;
called_getNum = false;
expect( instance.getNum() ).to.equal( 10 );
expect( called_getNum ).to.be.false;
called_setNum = false;
expect( instance.setNum( 100 ) ).to.equal( 100 );
expect( called_setNum ).to.be.false;
done();
} );
} );
suite( 'mixins', function() {
test( 'basic functionality', function( done ) {
function noop() {}
var expected_object = { num : 250 },
instance,
mixintest_ctor_called = false,
mixintest_mixin_called = false;
Class.define( 'MixinTest_01', {
constructor : function MixinTest_01() {
this.mixin( 'mixintest', arguments );
},
mixins : {
mixintest : {
constructor : function() {
mixintest_ctor_called = true;
},
bar : function() {
mixintest_mixin_called = true;
}
}
},
module : mod,
bar : function( arg1, arg2, arg3 ) {
expect( arg1 ).to.equal( 'foo' );
expect( arg2 ).to.equal( noop );
expect( arg3 ).to.equal( expected_object );
this.mixin( 'mixintest', arguments );
}
} );
instance = Class.new( 'MixinTest_01' );
expect( mixintest_ctor_called ).to.be.true;
instance.bar( 'foo', noop, expected_object );
expect( mixintest_mixin_called ).to.be.true;
done();
} );
test( 'mixins with inheritance', function( done ) {
var GenericMixin_01_foo_called = false,
GenericMixin_02_bar_called = false,
instance;
Class.define( 'GenericMixin_01', {
module : mod,
foo : function( foo ) {
GenericMixin_01_foo_called = true;
expect( foo ).to.equal( 'bar' );
return foo;
}
} );
mod.GenericMixin_02 = {
bar : function( bar ) {
GenericMixin_02_bar_called = true;
expect( bar ).to.equal( 'foo' );
}
};
Class.define( 'MixinTest_02', {
mixins : {
foo : mod.GenericMixin_01,
bar : mod.GenericMixin_02
},
module : mod,
bar : function() {
return this.mixin( 'foo', arguments ).mixin( 'bar', arguments );
}
} );
instance = new mod.MixinTest_02();
expect( instance.foo( 'bar' ) ).to.equal( 'bar' );
expect( instance.bar( 'foo' ) ).to.equal( instance );
expect( GenericMixin_01_foo_called ).to.be.true;
expect( GenericMixin_02_bar_called ).to.be.true;
done();
} );
} );
suite( 'extending from "classes" not created with `Class`', function() {
test( 'chaining', function( done ) {
function Foo() {
this.foo = 'foo';
}
Foo.prototype = {
constructor : Foo,
getFoo : function() {
return this.foo;
}
};
var Bar = Class( {
constructor : function() {
expect( this.parent() ).to.equal( this );
},
extend : Foo,
getFoo : function() {
return this.parent();
}
} ),
bar = new Bar();
expect( bar.getFoo() ).to.equal( 'foo' );
done();
} );
} );
} );