Merge branch 'master' into visibility/master
Conflicts: test/test-class-extend.jsclosure/master
commit
4148f8742d
24
README.md
24
README.md
|
@ -11,6 +11,7 @@ Current support includes:
|
||||||
* Classical inheritance
|
* Classical inheritance
|
||||||
* Abstract classes and methods
|
* Abstract classes and methods
|
||||||
* Interfaces
|
* Interfaces
|
||||||
|
* Near-completed visibility support in `visibility/master` branch
|
||||||
|
|
||||||
**This project is still under development.**
|
**This project is still under development.**
|
||||||
|
|
||||||
|
@ -26,7 +27,7 @@ itch.
|
||||||
Please note that, as the project is under active development, the API may change
|
Please note that, as the project is under active development, the API may change
|
||||||
until the first release.
|
until the first release.
|
||||||
|
|
||||||
This module uses the [CommonJS](http://commonjs.org) module format. In the
|
ease.js uses the [CommonJS](http://commonjs.org) module format. In the
|
||||||
examples below, [Node.js](http://nodejs.org) is used.
|
examples below, [Node.js](http://nodejs.org) is used.
|
||||||
|
|
||||||
### Creating Classes
|
### Creating Classes
|
||||||
|
@ -36,7 +37,7 @@ class. The constructor is provided as the `__construct()` method (influenced by
|
||||||
|
|
||||||
var Class = require( 'easejs' ).Class;
|
var Class = require( 'easejs' ).Class;
|
||||||
|
|
||||||
var Foo = Class.extend(
|
var Foo = Class(
|
||||||
{
|
{
|
||||||
foo: '',
|
foo: '',
|
||||||
|
|
||||||
|
@ -89,7 +90,7 @@ they contain one or more abstract methods.
|
||||||
|
|
||||||
var Class = require( 'easejs' ).Class;
|
var Class = require( 'easejs' ).Class;
|
||||||
|
|
||||||
var AbstractFoo = Class.extend(
|
var AbstractFoo = Class(
|
||||||
{
|
{
|
||||||
// a function may be provided if you wish the subtypes to implement a
|
// a function may be provided if you wish the subtypes to implement a
|
||||||
// certain number of arguments
|
// certain number of arguments
|
||||||
|
@ -139,6 +140,23 @@ The abstract methods are available as a read-only `abstractMethods` property.
|
||||||
StillAbstractFoo.isAbstract(); // true
|
StillAbstractFoo.isAbstract(); // true
|
||||||
|
|
||||||
|
|
||||||
|
### Interfaces
|
||||||
|
Interfaces can be declared in a very similar manner to classes. All members of
|
||||||
|
an interface must be declared as abstract.
|
||||||
|
|
||||||
|
var MyType = Interface(
|
||||||
|
{
|
||||||
|
'abstract foo': []
|
||||||
|
});
|
||||||
|
|
||||||
|
To implement an interface, use the `implement()` class method:
|
||||||
|
|
||||||
|
var ConcreteType = Class.implement( MyType ).extend(
|
||||||
|
{
|
||||||
|
foo: function() {}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
## Use of Reserved Words
|
## Use of Reserved Words
|
||||||
Though JavaScript doesn't currently implement classes, interfaces, etc, it does
|
Though JavaScript doesn't currently implement classes, interfaces, etc, it does
|
||||||
reserve the keywords. In an effort to ensure that ease.js will not clash, the
|
reserve the keywords. In an effort to ensure that ease.js will not clash, the
|
||||||
|
|
2
TODO
2
TODO
|
@ -12,6 +12,8 @@ Misc
|
||||||
functions, will not impact function logic.
|
functions, will not impact function logic.
|
||||||
- Should be able to run source file without preprocessing, so C-style macros
|
- Should be able to run source file without preprocessing, so C-style macros
|
||||||
cannot be used (# is not a valid token)
|
cannot be used (# is not a valid token)
|
||||||
|
- Class/Interface naming
|
||||||
|
- Will be especially helpful for error messages
|
||||||
|
|
||||||
Property Keywords
|
Property Keywords
|
||||||
- Restrictions; throw exceptions when unknown keywords are used
|
- Restrictions; throw exceptions when unknown keywords are used
|
||||||
|
|
130
lib/class.js
130
lib/class.js
|
@ -45,6 +45,66 @@ var class_meta = {};
|
||||||
var class_instance = {};
|
var class_instance = {};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This module may be invoked in order to provide a more natural looking class
|
||||||
|
* definition mechanism
|
||||||
|
*
|
||||||
|
* This may not be used to extend existing classes. To extend an existing class,
|
||||||
|
* use the class's extend() method. If unavailable (or extending a non-ease.js
|
||||||
|
* class/object), use the module's extend() method.
|
||||||
|
*
|
||||||
|
* @param {Object} def class definition
|
||||||
|
*
|
||||||
|
* @return {Class} new class
|
||||||
|
*/
|
||||||
|
module.exports = function()
|
||||||
|
{
|
||||||
|
var def = {},
|
||||||
|
name = '';
|
||||||
|
|
||||||
|
// anonymous class
|
||||||
|
if ( typeof arguments[ 0 ] === 'object' )
|
||||||
|
{
|
||||||
|
def = arguments[ 0 ];
|
||||||
|
|
||||||
|
// ensure we have the proper number of arguments (if they passed in too
|
||||||
|
// many, it may signify that they don't know what they're doing, and likely
|
||||||
|
// they're not getting the result they're looking for)
|
||||||
|
if ( arguments.length > 1 )
|
||||||
|
{
|
||||||
|
throw Error(
|
||||||
|
"Expecting one argument for Class definition; " +
|
||||||
|
arguments.length + " given."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// named class
|
||||||
|
else if ( typeof arguments[ 0 ] === 'string' )
|
||||||
|
{
|
||||||
|
name = arguments[ 0 ];
|
||||||
|
def = arguments[ 1 ];
|
||||||
|
|
||||||
|
// add the name to the definition
|
||||||
|
def.__name = name;
|
||||||
|
|
||||||
|
// the definition must be an object
|
||||||
|
if ( typeof def !== 'object' )
|
||||||
|
{
|
||||||
|
throw TypeError( "Unexpected value for named class definition" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// we don't know what to do!
|
||||||
|
throw TypeError(
|
||||||
|
"Expecting anonymous class definition or named class definition"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return extend( def );
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a class, inheriting either from the provided base class or the
|
* Creates a class, inheriting either from the provided base class or the
|
||||||
* default base class
|
* default base class
|
||||||
|
@ -53,7 +113,7 @@ var class_instance = {};
|
||||||
*
|
*
|
||||||
* @return {Object} extended class
|
* @return {Object} extended class
|
||||||
*/
|
*/
|
||||||
exports.extend = function( base )
|
module.exports.extend = function( base )
|
||||||
{
|
{
|
||||||
return extend.apply( this, arguments );
|
return extend.apply( this, arguments );
|
||||||
};
|
};
|
||||||
|
@ -66,12 +126,12 @@ exports.extend = function( base )
|
||||||
*
|
*
|
||||||
* @return {Class} new class containing interface abstractions
|
* @return {Class} new class containing interface abstractions
|
||||||
*/
|
*/
|
||||||
exports.implement = function()
|
module.exports.implement = function()
|
||||||
{
|
{
|
||||||
var args = Array.prototype.slice.call( arguments );
|
var args = Array.prototype.slice.call( arguments );
|
||||||
|
|
||||||
// apply to an empty (new) object
|
// apply to an empty (new) object
|
||||||
args.unshift( exports.extend() );
|
args.unshift( module.exports.extend() );
|
||||||
|
|
||||||
return implement.apply( this, args );
|
return implement.apply( this, args );
|
||||||
};
|
};
|
||||||
|
@ -84,7 +144,7 @@ exports.implement = function()
|
||||||
*
|
*
|
||||||
* @return {boolean} true if class (created through ease.js), otherwise false
|
* @return {boolean} true if class (created through ease.js), otherwise false
|
||||||
*/
|
*/
|
||||||
exports.isClass = function( obj )
|
module.exports.isClass = function( obj )
|
||||||
{
|
{
|
||||||
obj = obj || {};
|
obj = obj || {};
|
||||||
|
|
||||||
|
@ -104,7 +164,7 @@ exports.isClass = function( obj )
|
||||||
* @return {boolean} true if instance of class (created through ease.js),
|
* @return {boolean} true if instance of class (created through ease.js),
|
||||||
* otherwise false
|
* otherwise false
|
||||||
*/
|
*/
|
||||||
exports.isClassInstance = function( obj )
|
module.exports.isClassInstance = function( obj )
|
||||||
{
|
{
|
||||||
obj = obj || {};
|
obj = obj || {};
|
||||||
|
|
||||||
|
@ -127,7 +187,7 @@ exports.isClassInstance = function( obj )
|
||||||
*
|
*
|
||||||
* @return {boolean} true if instance is an instance of type, otherwise false
|
* @return {boolean} true if instance is an instance of type, otherwise false
|
||||||
*/
|
*/
|
||||||
exports.isInstanceOf = function( type, instance )
|
module.exports.isInstanceOf = function( type, instance )
|
||||||
{
|
{
|
||||||
var meta, implemented, i;
|
var meta, implemented, i;
|
||||||
|
|
||||||
|
@ -172,7 +232,7 @@ exports.isInstanceOf = function( type, instance )
|
||||||
* accurately conveys the act of inheritance, implementing interfaces and
|
* accurately conveys the act of inheritance, implementing interfaces and
|
||||||
* traits, etc.
|
* traits, etc.
|
||||||
*/
|
*/
|
||||||
exports.isA = exports.isInstanceOf;
|
module.exports.isA = module.exports.isInstanceOf;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -222,6 +282,7 @@ var extend = ( function( extending )
|
||||||
props = args.pop() || {},
|
props = args.pop() || {},
|
||||||
base = args.pop() || Class,
|
base = args.pop() || Class,
|
||||||
prototype = new base(),
|
prototype = new base(),
|
||||||
|
cname = '',
|
||||||
|
|
||||||
hasOwn = Array.prototype.hasOwnProperty;
|
hasOwn = Array.prototype.hasOwnProperty;
|
||||||
|
|
||||||
|
@ -234,6 +295,13 @@ var extend = ( function( extending )
|
||||||
|| { __length: 0 }
|
|| { __length: 0 }
|
||||||
;
|
;
|
||||||
|
|
||||||
|
// grab the name, if one was provided
|
||||||
|
if ( cname = props.__name )
|
||||||
|
{
|
||||||
|
// we no longer need it
|
||||||
|
delete props.__name;
|
||||||
|
}
|
||||||
|
|
||||||
util.propParse( props, {
|
util.propParse( props, {
|
||||||
each: function( name, value, keywords )
|
each: function( name, value, keywords )
|
||||||
{
|
{
|
||||||
|
@ -303,7 +371,7 @@ var extend = ( function( extending )
|
||||||
prototype.parent = base.prototype;
|
prototype.parent = base.prototype;
|
||||||
|
|
||||||
// set up the new class
|
// set up the new class
|
||||||
var new_class = createCtor( abstract_methods );
|
var new_class = createCtor( cname, abstract_methods );
|
||||||
|
|
||||||
attachPropInit( prototype, prop_init, members );
|
attachPropInit( prototype, prop_init, members );
|
||||||
|
|
||||||
|
@ -320,6 +388,7 @@ var extend = ( function( extending )
|
||||||
// create internal metadata for the new class
|
// create internal metadata for the new class
|
||||||
var meta = createMeta( new_class, base.prototype.__cid );
|
var meta = createMeta( new_class, base.prototype.__cid );
|
||||||
meta.abstractMethods = abstract_methods;
|
meta.abstractMethods = abstract_methods;
|
||||||
|
meta.name = cname;
|
||||||
|
|
||||||
// we're done with the extension process
|
// we're done with the extension process
|
||||||
extending = false;
|
extending = false;
|
||||||
|
@ -334,11 +403,12 @@ var extend = ( function( extending )
|
||||||
* This constructor will call the __constructor method for concrete classes
|
* This constructor will call the __constructor method for concrete classes
|
||||||
* and throw an exception for abstract classes (to prevent instantiation).
|
* and throw an exception for abstract classes (to prevent instantiation).
|
||||||
*
|
*
|
||||||
|
* @param {string} cname class name (may be empty)
|
||||||
* @param {Array.<string>} abstract_methods list of abstract methods
|
* @param {Array.<string>} abstract_methods list of abstract methods
|
||||||
*
|
*
|
||||||
* @return {Function} constructor
|
* @return {Function} constructor
|
||||||
*/
|
*/
|
||||||
function createCtor( abstract_methods )
|
function createCtor( cname, abstract_methods )
|
||||||
{
|
{
|
||||||
// concrete class
|
// concrete class
|
||||||
if ( abstract_methods.__length === 0 )
|
if ( abstract_methods.__length === 0 )
|
||||||
|
@ -375,13 +445,26 @@ var extend = ( function( extending )
|
||||||
// attach any instance properties/methods (done after
|
// attach any instance properties/methods (done after
|
||||||
// constructor to ensure they are not overridden)
|
// constructor to ensure they are not overridden)
|
||||||
attachInstanceOf( this );
|
attachInstanceOf( this );
|
||||||
|
|
||||||
|
// provide a more intuitive string representation of the class
|
||||||
|
// instance
|
||||||
|
this.toString = ( cname )
|
||||||
|
? function()
|
||||||
|
{
|
||||||
|
return '[object #<' + cname + '>]';
|
||||||
|
}
|
||||||
|
: function()
|
||||||
|
{
|
||||||
|
return '[object #<anonymous>]';
|
||||||
|
}
|
||||||
|
;
|
||||||
};
|
};
|
||||||
|
|
||||||
// provide a more intuitive string representation
|
// provide a more intuitive string representation
|
||||||
__self.toString = function()
|
__self.toString = ( cname )
|
||||||
{
|
? function() { return '[object Class <' + cname + '>]'; }
|
||||||
return '<Class>';
|
: function() { return '[object Class]'; }
|
||||||
};
|
;
|
||||||
|
|
||||||
return __self;
|
return __self;
|
||||||
}
|
}
|
||||||
|
@ -392,15 +475,20 @@ var extend = ( function( extending )
|
||||||
{
|
{
|
||||||
if ( !extending )
|
if ( !extending )
|
||||||
{
|
{
|
||||||
throw new Error( "Abstract classes cannot be instantiated" );
|
throw Error( "Abstract classes cannot be instantiated" );
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// provide a more intuitive string representation
|
__abstract_self.toString = ( cname )
|
||||||
__abstract_self.toString = function()
|
? function()
|
||||||
{
|
{
|
||||||
return '<Abstract Class>';
|
return '[object AbstractClass <' + cname + '>]';
|
||||||
};
|
}
|
||||||
|
: function()
|
||||||
|
{
|
||||||
|
return '[object AbstractClass]';
|
||||||
|
}
|
||||||
|
;
|
||||||
|
|
||||||
return __abstract_self;
|
return __abstract_self;
|
||||||
}
|
}
|
||||||
|
@ -446,7 +534,7 @@ var implement = function()
|
||||||
}
|
}
|
||||||
|
|
||||||
// create a new class with the implemented abstract methods
|
// create a new class with the implemented abstract methods
|
||||||
var class_new = exports.extend( base, dest );
|
var class_new = module.exports.extend( base, dest );
|
||||||
getMeta( class_new.__cid ).implemented = implemented;
|
getMeta( class_new.__cid ).implemented = implemented;
|
||||||
|
|
||||||
return class_new;
|
return class_new;
|
||||||
|
@ -456,7 +544,7 @@ var implement = function()
|
||||||
/**
|
/**
|
||||||
* Sets up common properties for the provided function (class)
|
* Sets up common properties for the provided function (class)
|
||||||
*
|
*
|
||||||
* @param {Function} func function (class) to set up
|
* @param {function()} func function (class) to set up
|
||||||
* @param {Array.<string>} abstract_methods list of abstract method names
|
* @param {Array.<string>} abstract_methods list of abstract method names
|
||||||
* @param {number} class_id unique id to assign to class
|
* @param {number} class_id unique id to assign to class
|
||||||
*
|
*
|
||||||
|
@ -694,7 +782,7 @@ function attachInstanceOf( instance )
|
||||||
{
|
{
|
||||||
var method = function( type )
|
var method = function( type )
|
||||||
{
|
{
|
||||||
return exports.isInstanceOf( type, instance );
|
return module.exports.isInstanceOf( type, instance );
|
||||||
};
|
};
|
||||||
|
|
||||||
util.defineSecureProp( instance, 'isInstanceOf', method );
|
util.defineSecureProp( instance, 'isInstanceOf', method );
|
||||||
|
|
|
@ -27,12 +27,49 @@ var util = require( './util' ),
|
||||||
Class = require( './class' );
|
Class = require( './class' );
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This module may be invoked in order to provide a more natural looking
|
||||||
|
* interface definition
|
||||||
|
*
|
||||||
|
* Only new interfaces may be created using this method. They cannot be
|
||||||
|
* extended. To extend an existing interface, call its extend() method, or use
|
||||||
|
* the extend() method of this module.
|
||||||
|
*
|
||||||
|
* @param {Object} def interface definition
|
||||||
|
*
|
||||||
|
* @return {Interface} new interface
|
||||||
|
*/
|
||||||
|
module.exports = function( def )
|
||||||
|
{
|
||||||
|
// if the first argument is an object, then we are declaring an interface
|
||||||
|
if ( typeof def !== 'object' )
|
||||||
|
{
|
||||||
|
throw TypeError(
|
||||||
|
"Must provide interface definition when declaring interface"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensure we have the proper number of arguments (if they passed in too
|
||||||
|
// many, it may signify that they don't know what they're doing, and likely
|
||||||
|
// they're not getting the result they're looking for)
|
||||||
|
if ( arguments.length > 1 )
|
||||||
|
{
|
||||||
|
throw Error(
|
||||||
|
"Expecting one argument for Interface definition; " +
|
||||||
|
arguments.length + " given."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return extend( def );
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an interface
|
* Creates an interface
|
||||||
*
|
*
|
||||||
* @return {Interface} extended interface
|
* @return {Interface} extended interface
|
||||||
*/
|
*/
|
||||||
exports.extend = function()
|
module.exports.extend = function()
|
||||||
{
|
{
|
||||||
return extend.apply( this, arguments );
|
return extend.apply( this, arguments );
|
||||||
};
|
};
|
||||||
|
|
|
@ -184,18 +184,6 @@ assert.throws( function()
|
||||||
}, TypeError, "Abstract methods must be declared as arrays" );
|
}, TypeError, "Abstract methods must be declared as arrays" );
|
||||||
|
|
||||||
|
|
||||||
// otherwise it'll output the internal constructor code, which is especially
|
|
||||||
// confusing since the user does not write it
|
|
||||||
( function testConvertingAbstractClassToStringYieldsClassString()
|
|
||||||
{
|
|
||||||
assert.equal(
|
|
||||||
Class.extend( { 'abstract foo': [] } ).toString(),
|
|
||||||
'<Abstract Class>',
|
|
||||||
"Converting abstract class to string yields class string"
|
|
||||||
);
|
|
||||||
} )();
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* There was an issue where the object holding the abstract methods list was not
|
* There was an issue where the object holding the abstract methods list was not
|
||||||
* checking for methods by using hasOwnProperty(). Therefore, if a method such
|
* checking for methods by using hasOwnProperty(). Therefore, if a method such
|
||||||
|
|
|
@ -30,7 +30,25 @@ var foo_props = {
|
||||||
one: 1,
|
one: 1,
|
||||||
two: 2,
|
two: 2,
|
||||||
},
|
},
|
||||||
Foo = Class.extend( foo_props );
|
|
||||||
|
// there are two different means of extending; we want to test them both
|
||||||
|
classes = [
|
||||||
|
Class.extend( foo_props ),
|
||||||
|
Class( foo_props ),
|
||||||
|
],
|
||||||
|
|
||||||
|
class_count = classes.length
|
||||||
|
|
||||||
|
// will hold the class being tested
|
||||||
|
Foo = null
|
||||||
|
;
|
||||||
|
|
||||||
|
|
||||||
|
// Run all tests for both. This will ensure that, regardless of how the class is
|
||||||
|
// created, it operates as it should. Fortunately, these tests are fairly quick.
|
||||||
|
for ( var i = 0; i < class_count; i++ )
|
||||||
|
{
|
||||||
|
Foo = classes[ i ];
|
||||||
|
|
||||||
assert.ok(
|
assert.ok(
|
||||||
( Foo.extend instanceof Function ),
|
( Foo.extend instanceof Function ),
|
||||||
|
@ -41,7 +59,10 @@ var sub_props = {
|
||||||
three: 3,
|
three: 3,
|
||||||
four: 4,
|
four: 4,
|
||||||
},
|
},
|
||||||
SubFoo = Foo.extend( sub_props );
|
|
||||||
|
SubFoo = Foo.extend( sub_props ),
|
||||||
|
sub_foo = SubFoo()
|
||||||
|
;
|
||||||
|
|
||||||
assert.ok(
|
assert.ok(
|
||||||
( SubFoo instanceof Object ),
|
( SubFoo instanceof Object ),
|
||||||
|
@ -63,7 +84,7 @@ for ( var prop in sub_props )
|
||||||
{
|
{
|
||||||
assert.equal(
|
assert.equal(
|
||||||
sub_props[ prop ],
|
sub_props[ prop ],
|
||||||
SubFoo()[ prop ],
|
sub_foo[ prop ],
|
||||||
"Subtype contains its own properties: " + prop
|
"Subtype contains its own properties: " + prop
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -223,16 +244,43 @@ assert.ok(
|
||||||
),
|
),
|
||||||
"Instances of subtypes do not share property references"
|
"Instances of subtypes do not share property references"
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// otherwise it'll output the internal constructor code, which is especially
|
( function testInvokingClassModuleRequiresObjectAsArgumentIfCreating()
|
||||||
// confusing since the user does not write it
|
|
||||||
( function testConvertingClassToStringYieldsClassString()
|
|
||||||
{
|
{
|
||||||
assert.equal(
|
assert.throws( function()
|
||||||
Class.extend( {} ).toString(),
|
{
|
||||||
'<Class>',
|
Class( 'moo' );
|
||||||
"Converting class to string yields class string"
|
Class( 5 );
|
||||||
|
Class( false );
|
||||||
|
Class();
|
||||||
|
},
|
||||||
|
TypeError,
|
||||||
|
"Invoking class module requires object as argument if extending " +
|
||||||
|
"from base class"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
var args = [ {}, 'one', 'two', 'three' ];
|
||||||
|
|
||||||
|
// we must only provide one argument if the first argument is an object (the
|
||||||
|
// class definition)
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Class.apply( null, args );
|
||||||
|
|
||||||
|
// if all goes well, we don't get to this line
|
||||||
|
assert.fail(
|
||||||
|
"Only one argument for class definitions is permitted"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
catch ( e )
|
||||||
|
{
|
||||||
|
assert.notEqual(
|
||||||
|
e.toString().match( args.length + ' given' ),
|
||||||
|
null,
|
||||||
|
"Class invocation should give argument count on error"
|
||||||
|
);
|
||||||
|
}
|
||||||
} )();
|
} )();
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,130 @@
|
||||||
|
/**
|
||||||
|
* Tests class naming
|
||||||
|
*
|
||||||
|
* Copyright (C) 2010 Mike Gerwitz
|
||||||
|
*
|
||||||
|
* This file is part of ease.js.
|
||||||
|
*
|
||||||
|
* ease.js is free software: you can redistribute it and/or modify it under the
|
||||||
|
* terms of the GNU Lesser General Public License as published by the Free
|
||||||
|
* Software Foundation, either version 3 of the License, or (at your option)
|
||||||
|
* any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||||
|
* for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* @author Mike Gerwitz
|
||||||
|
* @package test
|
||||||
|
*/
|
||||||
|
|
||||||
|
var common = require( './common' ),
|
||||||
|
assert = require( 'assert' ),
|
||||||
|
Class = common.require( 'class' )
|
||||||
|
;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Classes may be named by passing the name as the first argument to the module
|
||||||
|
*/
|
||||||
|
( function testClassAcceptsName()
|
||||||
|
{
|
||||||
|
assert.doesNotThrow( function()
|
||||||
|
{
|
||||||
|
var cls = Class( 'Foo', {} );
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
Class.isClass( cls ),
|
||||||
|
true,
|
||||||
|
"Class defined with name is returned as a valid class"
|
||||||
|
);
|
||||||
|
}, Error, "Class accepts name" );
|
||||||
|
|
||||||
|
// the second argument must be an object
|
||||||
|
assert.throws( function()
|
||||||
|
{
|
||||||
|
Class( 'Foo', 'Bar' );
|
||||||
|
}, TypeError, "Second argument to named class must be the definition" );
|
||||||
|
} )();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* By default, anonymous classes should just state that they are a class when
|
||||||
|
* they are converted to a string
|
||||||
|
*/
|
||||||
|
( function testConvertingAnonymousClassToStringYieldsClassString()
|
||||||
|
{
|
||||||
|
// concrete
|
||||||
|
assert.equal(
|
||||||
|
Class( {} ).toString(),
|
||||||
|
'[object Class]',
|
||||||
|
"Converting anonymous class to string yields class string"
|
||||||
|
);
|
||||||
|
|
||||||
|
// abstract
|
||||||
|
assert.equal(
|
||||||
|
Class( { 'abstract foo': [] } ).toString(),
|
||||||
|
'[object AbstractClass]',
|
||||||
|
"Converting abstract anonymous class to string yields class string"
|
||||||
|
);
|
||||||
|
} )();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the class is named, then the name should be presented when it is converted
|
||||||
|
* to a string
|
||||||
|
*/
|
||||||
|
( function testConvertingNamedClassToStringYieldsClassStringContainingName()
|
||||||
|
{
|
||||||
|
var name = 'Foo';
|
||||||
|
|
||||||
|
// concrete
|
||||||
|
assert.equal(
|
||||||
|
Class( name, {} ).toString(),
|
||||||
|
'[object Class <' + name + '>]',
|
||||||
|
"Converting named class to string yields string with name of class"
|
||||||
|
);
|
||||||
|
|
||||||
|
// abstract
|
||||||
|
assert.equal(
|
||||||
|
Class( name, { 'abstract foo': [] } ).toString(),
|
||||||
|
'[object AbstractClass <' + name + '>]',
|
||||||
|
"Converting abstract named class to string yields string with name " +
|
||||||
|
"of class"
|
||||||
|
);
|
||||||
|
} )();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class instances are displayed differently than uninstantiated classes.
|
||||||
|
* Mainly, they output that they are an object, in addition to the class name.
|
||||||
|
*/
|
||||||
|
( function testConvertingClassInstanceToStringYieldsInstanceString()
|
||||||
|
{
|
||||||
|
var name = 'Foo',
|
||||||
|
|
||||||
|
anon = Class( {} )(),
|
||||||
|
named = Class( name, {} )()
|
||||||
|
;
|
||||||
|
|
||||||
|
// anonymous
|
||||||
|
assert.equal(
|
||||||
|
anon.toString(),
|
||||||
|
'[object #<anonymous>]',
|
||||||
|
"Converting anonymous class instance to string yields string " +
|
||||||
|
"indiciating that the class is anonymous"
|
||||||
|
);
|
||||||
|
|
||||||
|
// named
|
||||||
|
assert.equal(
|
||||||
|
named.toString(),
|
||||||
|
'[object #<' + name + '>]',
|
||||||
|
"Converting named class instance to string yields string with name " +
|
||||||
|
"of class"
|
||||||
|
);
|
||||||
|
} )();
|
||||||
|
|
|
@ -83,10 +83,22 @@ assert.doesNotThrow(
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
var BaseType = Interface.extend(
|
// There's a couple ways to create interfaces. Test 'em both.
|
||||||
|
var base_types = [
|
||||||
|
Interface.extend(
|
||||||
{
|
{
|
||||||
'abstract method': [],
|
'abstract method': [],
|
||||||
});
|
} ),
|
||||||
|
|
||||||
|
Interface( {
|
||||||
|
'abstract method': [],
|
||||||
|
} )
|
||||||
|
];
|
||||||
|
|
||||||
|
var BaseType;
|
||||||
|
for ( var i = 0; i < base_types.length; i++ )
|
||||||
|
{
|
||||||
|
BaseType = base_types[ i ];
|
||||||
|
|
||||||
assert.ok(
|
assert.ok(
|
||||||
( BaseType.prototype.method instanceof Function ),
|
( BaseType.prototype.method instanceof Function ),
|
||||||
|
@ -136,4 +148,48 @@ assert.ok(
|
||||||
"Interfaces can be extended with additional abstract methods using " +
|
"Interfaces can be extended with additional abstract methods using " +
|
||||||
"shorthand extend method"
|
"shorthand extend method"
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The interface invocation action depends on what arguments are passed in. One
|
||||||
|
* use is to pass in an object as the first and only argument, creating a new
|
||||||
|
* interface with no supertype.
|
||||||
|
*/
|
||||||
|
( function testInvokingInterfaceModuleRequiresObjectAsArgumentIfExtending()
|
||||||
|
{
|
||||||
|
assert.throws( function()
|
||||||
|
{
|
||||||
|
Interface( 'moo' );
|
||||||
|
Interface( 5 );
|
||||||
|
Interface( false );
|
||||||
|
Interface();
|
||||||
|
},
|
||||||
|
TypeError,
|
||||||
|
"Invoking interface module requires object as argument if extending " +
|
||||||
|
"from base interface"
|
||||||
|
);
|
||||||
|
|
||||||
|
var args = [ {}, 'one', 'two', 'three' ];
|
||||||
|
|
||||||
|
// we must only provide one argument if the first argument is an object (the
|
||||||
|
// interface definition)
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Interface.apply( null, args );
|
||||||
|
|
||||||
|
// if all goes well, we don't get to this line
|
||||||
|
assert.fail(
|
||||||
|
"Only one argument for interface definitions is permitted"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
catch ( e )
|
||||||
|
{
|
||||||
|
assert.notEqual(
|
||||||
|
e.toString().match( args.length + ' given' ),
|
||||||
|
null,
|
||||||
|
"Interface invocation should give argument count on error"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} )();
|
||||||
|
|
||||||
|
|
|
@ -84,13 +84,14 @@ for module in $CAT_MODULES; do
|
||||||
|
|
||||||
# each module must be enclosed in a closure to emulate a module
|
# each module must be enclosed in a closure to emulate a module
|
||||||
echo "/** $module **/"
|
echo "/** $module **/"
|
||||||
echo "( function( exports )"
|
echo "( function( module )"
|
||||||
echo "{"
|
echo "{"
|
||||||
|
echo " var exports = module.exports = {};"
|
||||||
|
|
||||||
# add the module, removing trailing commas
|
# add the module, removing trailing commas
|
||||||
cat $filename | $RMTRAIL
|
cat $filename | $RMTRAIL
|
||||||
|
|
||||||
echo "} )( exports['$module'] = {} );"
|
echo "} )( module['$module'] = {} );"
|
||||||
done
|
done
|
||||||
|
|
||||||
# include tests?
|
# include tests?
|
||||||
|
|
|
@ -18,12 +18,12 @@
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
# #
|
# #
|
||||||
|
|
||||||
exports.common = {
|
module.common = { exports: {
|
||||||
require: function ( id )
|
require: function ( id )
|
||||||
{
|
{
|
||||||
return require( id );
|
return require( id );
|
||||||
}
|
}
|
||||||
};
|
} };
|
||||||
|
|
||||||
|
|
||||||
function failAssertion( err )
|
function failAssertion( err )
|
||||||
|
@ -37,7 +37,7 @@ function failAssertion( err )
|
||||||
*
|
*
|
||||||
* This contains only the used assertions
|
* This contains only the used assertions
|
||||||
*/
|
*/
|
||||||
exports.assert = {
|
module.assert = { exports: {
|
||||||
equal: function ( val, cmp, err )
|
equal: function ( val, cmp, err )
|
||||||
{
|
{
|
||||||
if ( val !== cmp )
|
if ( val !== cmp )
|
||||||
|
@ -101,5 +101,5 @@ exports.assert = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
} };
|
||||||
|
|
||||||
|
|
|
@ -51,7 +51,7 @@ var easejs = {};
|
||||||
*
|
*
|
||||||
* @type {Object.<string,Object>}
|
* @type {Object.<string,Object>}
|
||||||
*/
|
*/
|
||||||
var exports = {};
|
var module = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the requested module
|
* Returns the requested module
|
||||||
|
@ -71,19 +71,19 @@ var easejs = {};
|
||||||
var id_clean = module_id.replace( /^.\//, '' );
|
var id_clean = module_id.replace( /^.\//, '' );
|
||||||
|
|
||||||
// attempt to retrieve the module
|
// attempt to retrieve the module
|
||||||
var module = exports[ id_clean ];
|
var mod = module[ id_clean ];
|
||||||
if ( module === undefined )
|
if ( mod === undefined )
|
||||||
{
|
{
|
||||||
throw "[ease.js] Undefined module: " + module_id;
|
throw "[ease.js] Undefined module: " + module_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
return module;
|
return mod.exports;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**{CONTENT}**/
|
/**{CONTENT}**/
|
||||||
|
|
||||||
// the following should match the exports of /index.js
|
// the following should match the exports of /index.js
|
||||||
ns_exports.Class = exports['class'];
|
ns_exports.Class = module['class'].exports;
|
||||||
ns_exports.Interface = exports['interface'];
|
ns_exports.Interface = module['interface'].exports;
|
||||||
} )( easejs );
|
} )( easejs );
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue