1
0
Fork 0

Merge branch 'master' into visibility/master

Conflicts:
	test/test-class-extend.js
closure/master
Mike Gerwitz 2011-03-03 23:29:20 -05:00
commit 4148f8742d
11 changed files with 653 additions and 285 deletions

View File

@ -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
View File

@ -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

View File

@ -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 );

View File

@ -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 );
}; };

View File

@ -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

View File

@ -30,209 +30,257 @@ var foo_props = {
one: 1, one: 1,
two: 2, two: 2,
}, },
Foo = Class.extend( foo_props );
assert.ok( // 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(
( Foo.extend instanceof Function ), ( Foo.extend instanceof Function ),
"Created class contains extend method" "Created class contains extend method"
); );
var sub_props = { var sub_props = {
three: 3, three: 3,
four: 4, four: 4,
}, },
SubFoo = Foo.extend( sub_props );
assert.ok( SubFoo = Foo.extend( sub_props ),
sub_foo = SubFoo()
;
assert.ok(
( SubFoo instanceof Object ), ( SubFoo instanceof Object ),
"Subtype is returned as an object" "Subtype is returned as an object"
); );
// ensure properties were inherited from supertype // ensure properties were inherited from supertype
for ( var prop in foo_props ) for ( var prop in foo_props )
{ {
assert.equal( assert.equal(
foo_props[ prop ], foo_props[ prop ],
SubFoo.prototype[ prop ], SubFoo.prototype[ prop ],
"Subtype inherits parent properties: " + prop "Subtype inherits parent properties: " + prop
); );
} }
// and ensure that the subtype's properties were included // and ensure that the subtype's properties were included
for ( var prop in sub_props ) 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
); );
} }
var sub_instance = new SubFoo(); var sub_instance = new SubFoo();
assert.ok( assert.ok(
( sub_instance instanceof Foo ), ( sub_instance instanceof Foo ),
"Subtypes are considered to be instances of their supertypes " + "Subtypes are considered to be instances of their supertypes " +
"(via instanceof operator)" "(via instanceof operator)"
); );
assert.ok( assert.ok(
sub_instance.isInstanceOf( SubFoo ), sub_instance.isInstanceOf( SubFoo ),
"Subtypes are considered to be instances of their supertypes (via " + "Subtypes are considered to be instances of their supertypes (via " +
"isInstanceOf method)" "isInstanceOf method)"
); );
// Foo // Foo
// | // |
// SubFoo // SubFoo
// / \ // / \
// SubSubFoo SubSubFoo2 // SubSubFoo SubSubFoo2
// //
var SubSubFoo = SubFoo.extend(), var SubSubFoo = SubFoo.extend(),
SubSubFoo2 = SubFoo.extend(), SubSubFoo2 = SubFoo.extend(),
sub_sub_instance = new SubSubFoo(), sub_sub_instance = new SubSubFoo(),
sub_sub2_instance = new SubSubFoo2(); sub_sub2_instance = new SubSubFoo2();
assert.ok( assert.ok(
( ( sub_sub_instance instanceof Foo ) ( ( sub_sub_instance instanceof Foo )
&& sub_sub_instance.isInstanceOf( Foo ) && sub_sub_instance.isInstanceOf( Foo )
), ),
"Sub-subtypes should be instances of their super-supertype" "Sub-subtypes should be instances of their super-supertype"
); );
assert.ok( assert.ok(
( !( sub_instance instanceof SubSubFoo ) ( !( sub_instance instanceof SubSubFoo )
&& !( sub_instance.isInstanceOf( SubSubFoo ) ) && !( sub_instance.isInstanceOf( SubSubFoo ) )
), ),
"Supertypes should not be considered instances of their subtypes" "Supertypes should not be considered instances of their subtypes"
); );
assert.ok( assert.ok(
( !( sub_sub2_instance instanceof SubSubFoo ) ( !( sub_sub2_instance instanceof SubSubFoo )
&& !( sub_sub2_instance.isInstanceOf( SubSubFoo ) ) && !( sub_sub2_instance.isInstanceOf( SubSubFoo ) )
), ),
"Subtypes should not be considered instances of their siblings" "Subtypes should not be considered instances of their siblings"
); );
// to test inheritance of classes that were not previously created via the // to test inheritance of classes that were not previously created via the
// Class.extend() method // Class.extend() method
var OtherClass = function() {}; var OtherClass = function() {};
OtherClass.prototype = OtherClass.prototype =
{ {
foo: 'bla', foo: 'bla',
}; };
var SubOther = Class.extend( OtherClass, var SubOther = Class.extend( OtherClass,
{ {
newFoo: 2, newFoo: 2,
}); });
assert.equal( assert.equal(
SubOther.prototype.foo, SubOther.prototype.foo,
OtherClass.prototype.foo, OtherClass.prototype.foo,
"Prototype of existing class should be copied to subclass" "Prototype of existing class should be copied to subclass"
); );
assert.notEqual( assert.notEqual(
SubOther().newFoo, SubOther().newFoo,
undefined, undefined,
"Subtype should contain extended members" "Subtype should contain extended members"
); );
assert.throws( function() assert.throws( function()
{ {
Class.extend( OtherClass, Class.extend( OtherClass,
{ {
foo: function() {}, foo: function() {},
}); });
}, TypeError, "Cannot override property with a method" ); }, TypeError, "Cannot override property with a method" );
var AnotherFoo = Class.extend( var AnotherFoo = Class.extend(
{ {
arr: [], arr: [],
obj: {}, obj: {},
}); });
var Obj1 = new AnotherFoo(), var Obj1 = new AnotherFoo(),
Obj2 = new AnotherFoo(); Obj2 = new AnotherFoo();
Obj1.arr.push( 'one' ); Obj1.arr.push( 'one' );
Obj2.arr.push( 'two' ); Obj2.arr.push( 'two' );
Obj1.obj.a = true; Obj1.obj.a = true;
Obj2.obj.b = true; Obj2.obj.b = true;
// to ensure we're not getting/setting values of the prototype (=== can also be // to ensure we're not getting/setting values of the prototype (=== can also be
// used to test for references, but this test demonstrates the functionality // used to test for references, but this test demonstrates the functionality
// that we're looking to ensure) // that we're looking to ensure)
assert.ok( assert.ok(
( ( Obj1.arr[ 0 ] === 'one' ) && ( Obj2.arr[ 0 ] === 'two' ) ), ( ( Obj1.arr[ 0 ] === 'one' ) && ( Obj2.arr[ 0 ] === 'two' ) ),
"Multiple instances of the same class do not share array references" "Multiple instances of the same class do not share array references"
); );
assert.ok( assert.ok(
( ( ( Obj1.obj.a === true ) && ( Obj1.obj.b === undefined ) ) ( ( ( Obj1.obj.a === true ) && ( Obj1.obj.b === undefined ) )
&& ( ( Obj2.obj.a === undefined ) && ( Obj2.obj.b === true ) ) && ( ( Obj2.obj.a === undefined ) && ( Obj2.obj.b === true ) )
), ),
"Multiple instances of the same class do not share object references" "Multiple instances of the same class do not share object references"
); );
var arr_val = 1; var arr_val = 1;
var SubAnotherFoo = AnotherFoo.extend( var SubAnotherFoo = AnotherFoo.extend(
{ {
arr: [ arr_val ], arr: [ arr_val ],
}); });
var SubObj1 = new SubAnotherFoo(), var SubObj1 = new SubAnotherFoo(),
SubObj2 = new SubAnotherFoo(); SubObj2 = new SubAnotherFoo();
assert.ok( assert.ok(
( ( SubObj1.arr !== SubObj2.arr ) && ( SubObj1.obj !== SubObj2.obj ) ), ( ( SubObj1.arr !== SubObj2.arr ) && ( SubObj1.obj !== SubObj2.obj ) ),
"Instances of subtypes do not share property references" "Instances of subtypes do not share property references"
); );
assert.ok( assert.ok(
( ( SubObj1.arr[ 0 ] === arr_val ) && ( SubObj2.arr[ 0 ] === arr_val ) ), ( ( SubObj1.arr[ 0 ] === arr_val ) && ( SubObj2.arr[ 0 ] === arr_val ) ),
"Subtypes can override parent property values" "Subtypes can override parent property values"
); );
assert.throws( function() assert.throws( function()
{ {
Class.extend( Class.extend(
{ {
__initProps: function() {}, __initProps: function() {},
}); });
}, Error, "__initProps() cannot be declared (internal method)" ); }, Error, "__initProps() cannot be declared (internal method)" );
var SubSubAnotherFoo = AnotherFoo.extend(), var SubSubAnotherFoo = AnotherFoo.extend(),
SubSubObj1 = new SubSubAnotherFoo(), SubSubObj1 = new SubSubAnotherFoo(),
SubSubObj2 = new SubSubAnotherFoo(); SubSubObj2 = new SubSubAnotherFoo();
// to ensure the effect is recursive // to ensure the effect is recursive
assert.ok( assert.ok(
( ( SubSubObj1.arr !== SubSubObj2.arr ) ( ( SubSubObj1.arr !== SubSubObj2.arr )
&& ( SubSubObj1.obj !== SubSubObj2.obj ) && ( SubSubObj1.obj !== SubSubObj2.obj )
), ),
"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
// confusing since the user does not write it
( function testConvertingClassToStringYieldsClassString()
{
assert.equal(
Class.extend( {} ).toString(),
'<Class>',
"Converting class to string yields class string"
); );
}
( function testInvokingClassModuleRequiresObjectAsArgumentIfCreating()
{
assert.throws( function()
{
Class( 'moo' );
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"
);
}
} )(); } )();

View File

@ -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"
);
} )();

View File

@ -83,57 +83,113 @@ 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': [],
}); } ),
assert.ok( Interface( {
'abstract method': [],
} )
];
var BaseType;
for ( var i = 0; i < base_types.length; i++ )
{
BaseType = base_types[ i ];
assert.ok(
( BaseType.prototype.method instanceof Function ), ( BaseType.prototype.method instanceof Function ),
"Interface contains defined abstract methods" "Interface contains defined abstract methods"
); );
var SubType = Interface.extend( BaseType, var SubType = Interface.extend( BaseType,
{ {
'abstract second': [], 'abstract second': [],
}); });
assert.ok( assert.ok(
( SubType.prototype instanceof BaseType ), ( SubType.prototype instanceof BaseType ),
"Generic interface extend method can extend from other interfaces" "Generic interface extend method can extend from other interfaces"
); );
assert.ok( assert.ok(
( SubType.prototype.method === BaseType.prototype.method ), ( SubType.prototype.method === BaseType.prototype.method ),
"Interface subtypes inherit abstract methods" "Interface subtypes inherit abstract methods"
); );
assert.ok( assert.ok(
( SubType.prototype.second instanceof Function ), ( SubType.prototype.second instanceof Function ),
"Interfaces can be extended with additional abstract methods" "Interfaces can be extended with additional abstract methods"
); );
assert.ok( assert.ok(
( BaseType.extend instanceof Function ), ( BaseType.extend instanceof Function ),
"Interface contains extend method" "Interface contains extend method"
); );
var SubType2 = BaseType.extend( var SubType2 = BaseType.extend(
{ {
'abstract second': [], 'abstract second': [],
}); });
assert.ok( assert.ok(
( SubType2.prototype instanceof BaseType ), ( SubType2.prototype instanceof BaseType ),
"Interface extend method can extend interfaces" "Interface extend method can extend interfaces"
); );
assert.ok( assert.ok(
( SubType2.prototype.second instanceof Function ), ( SubType2.prototype.second instanceof Function ),
"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"
);
}
} )();

View File

@ -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?

View File

@ -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 = {
} }
} }
}, },
}; } };

View File

@ -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 );