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
* Abstract classes and methods
* Interfaces
* Near-completed visibility support in `visibility/master` branch
**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
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.
### Creating Classes
@ -36,7 +37,7 @@ class. The constructor is provided as the `__construct()` method (influenced by
var Class = require( 'easejs' ).Class;
var Foo = Class.extend(
var Foo = Class(
{
foo: '',
@ -89,7 +90,7 @@ they contain one or more abstract methods.
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
// certain number of arguments
@ -139,6 +140,23 @@ The abstract methods are available as a read-only `abstractMethods` property.
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
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

2
TODO
View File

@ -12,6 +12,8 @@ Misc
functions, will not impact function logic.
- Should be able to run source file without preprocessing, so C-style macros
cannot be used (# is not a valid token)
- Class/Interface naming
- Will be especially helpful for error messages
Property Keywords
- Restrictions; throw exceptions when unknown keywords are used

View File

@ -45,6 +45,66 @@ var class_meta = {};
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
* default base class
@ -53,7 +113,7 @@ var class_instance = {};
*
* @return {Object} extended class
*/
exports.extend = function( base )
module.exports.extend = function( base )
{
return extend.apply( this, arguments );
};
@ -66,12 +126,12 @@ exports.extend = function( base )
*
* @return {Class} new class containing interface abstractions
*/
exports.implement = function()
module.exports.implement = function()
{
var args = Array.prototype.slice.call( arguments );
// apply to an empty (new) object
args.unshift( exports.extend() );
args.unshift( module.exports.extend() );
return implement.apply( this, args );
};
@ -84,7 +144,7 @@ exports.implement = function()
*
* @return {boolean} true if class (created through ease.js), otherwise false
*/
exports.isClass = function( obj )
module.exports.isClass = function( obj )
{
obj = obj || {};
@ -104,7 +164,7 @@ exports.isClass = function( obj )
* @return {boolean} true if instance of class (created through ease.js),
* otherwise false
*/
exports.isClassInstance = function( obj )
module.exports.isClassInstance = function( obj )
{
obj = obj || {};
@ -127,7 +187,7 @@ exports.isClassInstance = function( obj )
*
* @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;
@ -172,7 +232,7 @@ exports.isInstanceOf = function( type, instance )
* accurately conveys the act of inheritance, implementing interfaces and
* traits, etc.
*/
exports.isA = exports.isInstanceOf;
module.exports.isA = module.exports.isInstanceOf;
/**
@ -222,6 +282,7 @@ var extend = ( function( extending )
props = args.pop() || {},
base = args.pop() || Class,
prototype = new base(),
cname = '',
hasOwn = Array.prototype.hasOwnProperty;
@ -234,6 +295,13 @@ var extend = ( function( extending )
|| { __length: 0 }
;
// grab the name, if one was provided
if ( cname = props.__name )
{
// we no longer need it
delete props.__name;
}
util.propParse( props, {
each: function( name, value, keywords )
{
@ -303,7 +371,7 @@ var extend = ( function( extending )
prototype.parent = base.prototype;
// set up the new class
var new_class = createCtor( abstract_methods );
var new_class = createCtor( cname, abstract_methods );
attachPropInit( prototype, prop_init, members );
@ -320,6 +388,7 @@ var extend = ( function( extending )
// create internal metadata for the new class
var meta = createMeta( new_class, base.prototype.__cid );
meta.abstractMethods = abstract_methods;
meta.name = cname;
// we're done with the extension process
extending = false;
@ -334,11 +403,12 @@ var extend = ( function( extending )
* This constructor will call the __constructor method for concrete classes
* 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
*
* @return {Function} constructor
*/
function createCtor( abstract_methods )
function createCtor( cname, abstract_methods )
{
// concrete class
if ( abstract_methods.__length === 0 )
@ -375,13 +445,26 @@ var extend = ( function( extending )
// attach any instance properties/methods (done after
// constructor to ensure they are not overridden)
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
__self.toString = function()
{
return '<Class>';
};
__self.toString = ( cname )
? function() { return '[object Class <' + cname + '>]'; }
: function() { return '[object Class]'; }
;
return __self;
}
@ -392,15 +475,20 @@ var extend = ( function( 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 = function()
{
return '<Abstract Class>';
};
__abstract_self.toString = ( cname )
? function()
{
return '[object AbstractClass <' + cname + '>]';
}
: function()
{
return '[object AbstractClass]';
}
;
return __abstract_self;
}
@ -446,7 +534,7 @@ var implement = function()
}
// 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;
return class_new;
@ -456,7 +544,7 @@ var implement = function()
/**
* 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 {number} class_id unique id to assign to class
*
@ -694,7 +782,7 @@ function attachInstanceOf( instance )
{
var method = function( type )
{
return exports.isInstanceOf( type, instance );
return module.exports.isInstanceOf( type, instance );
};
util.defineSecureProp( instance, 'isInstanceOf', method );

View File

@ -27,12 +27,49 @@ var util = require( './util' ),
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
*
* @return {Interface} extended interface
*/
exports.extend = function()
module.exports.extend = function()
{
return extend.apply( this, arguments );
};

View File

@ -184,18 +184,6 @@ assert.throws( function()
}, 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
* checking for methods by using hasOwnProperty(). Therefore, if a method such

View File

@ -30,209 +30,257 @@ var foo_props = {
one: 1,
two: 2,
},
Foo = Class.extend( foo_props );
assert.ok(
( Foo.extend instanceof Function ),
"Created class contains extend method"
);
// there are two different means of extending; we want to test them both
classes = [
Class.extend( foo_props ),
Class( foo_props ),
],
var sub_props = {
three: 3,
four: 4,
},
SubFoo = Foo.extend( sub_props );
class_count = classes.length
assert.ok(
( SubFoo instanceof Object ),
"Subtype is returned as an object"
);
// will hold the class being tested
Foo = null
;
// ensure properties were inherited from supertype
for ( var prop in foo_props )
// 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++ )
{
assert.equal(
foo_props[ prop ],
SubFoo.prototype[ prop ],
"Subtype inherits parent properties: " + prop
Foo = classes[ i ];
assert.ok(
( Foo.extend instanceof Function ),
"Created class contains extend method"
);
}
// and ensure that the subtype's properties were included
for ( var prop in sub_props )
{
var sub_props = {
three: 3,
four: 4,
},
SubFoo = Foo.extend( sub_props ),
sub_foo = SubFoo()
;
assert.ok(
( SubFoo instanceof Object ),
"Subtype is returned as an object"
);
// ensure properties were inherited from supertype
for ( var prop in foo_props )
{
assert.equal(
foo_props[ prop ],
SubFoo.prototype[ prop ],
"Subtype inherits parent properties: " + prop
);
}
// and ensure that the subtype's properties were included
for ( var prop in sub_props )
{
assert.equal(
sub_props[ prop ],
sub_foo[ prop ],
"Subtype contains its own properties: " + prop
);
}
var sub_instance = new SubFoo();
assert.ok(
( sub_instance instanceof Foo ),
"Subtypes are considered to be instances of their supertypes " +
"(via instanceof operator)"
);
assert.ok(
sub_instance.isInstanceOf( SubFoo ),
"Subtypes are considered to be instances of their supertypes (via " +
"isInstanceOf method)"
);
// Foo
// |
// SubFoo
// / \
// SubSubFoo SubSubFoo2
//
var SubSubFoo = SubFoo.extend(),
SubSubFoo2 = SubFoo.extend(),
sub_sub_instance = new SubSubFoo(),
sub_sub2_instance = new SubSubFoo2();
assert.ok(
( ( sub_sub_instance instanceof Foo )
&& sub_sub_instance.isInstanceOf( Foo )
),
"Sub-subtypes should be instances of their super-supertype"
);
assert.ok(
( !( sub_instance instanceof SubSubFoo )
&& !( sub_instance.isInstanceOf( SubSubFoo ) )
),
"Supertypes should not be considered instances of their subtypes"
);
assert.ok(
( !( sub_sub2_instance instanceof SubSubFoo )
&& !( sub_sub2_instance.isInstanceOf( SubSubFoo ) )
),
"Subtypes should not be considered instances of their siblings"
);
// to test inheritance of classes that were not previously created via the
// Class.extend() method
var OtherClass = function() {};
OtherClass.prototype =
{
foo: 'bla',
};
var SubOther = Class.extend( OtherClass,
{
newFoo: 2,
});
assert.equal(
sub_props[ prop ],
SubFoo()[ prop ],
"Subtype contains its own properties: " + prop
SubOther.prototype.foo,
OtherClass.prototype.foo,
"Prototype of existing class should be copied to subclass"
);
assert.notEqual(
SubOther().newFoo,
undefined,
"Subtype should contain extended members"
);
assert.throws( function()
{
Class.extend( OtherClass,
{
foo: function() {},
});
}, TypeError, "Cannot override property with a method" );
var AnotherFoo = Class.extend(
{
arr: [],
obj: {},
});
var Obj1 = new AnotherFoo(),
Obj2 = new AnotherFoo();
Obj1.arr.push( 'one' );
Obj2.arr.push( 'two' );
Obj1.obj.a = true;
Obj2.obj.b = true;
// 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
// that we're looking to ensure)
assert.ok(
( ( Obj1.arr[ 0 ] === 'one' ) && ( Obj2.arr[ 0 ] === 'two' ) ),
"Multiple instances of the same class do not share array references"
);
assert.ok(
( ( ( Obj1.obj.a === true ) && ( Obj1.obj.b === undefined ) )
&& ( ( Obj2.obj.a === undefined ) && ( Obj2.obj.b === true ) )
),
"Multiple instances of the same class do not share object references"
);
var arr_val = 1;
var SubAnotherFoo = AnotherFoo.extend(
{
arr: [ arr_val ],
});
var SubObj1 = new SubAnotherFoo(),
SubObj2 = new SubAnotherFoo();
assert.ok(
( ( SubObj1.arr !== SubObj2.arr ) && ( SubObj1.obj !== SubObj2.obj ) ),
"Instances of subtypes do not share property references"
);
assert.ok(
( ( SubObj1.arr[ 0 ] === arr_val ) && ( SubObj2.arr[ 0 ] === arr_val ) ),
"Subtypes can override parent property values"
);
assert.throws( function()
{
Class.extend(
{
__initProps: function() {},
});
}, Error, "__initProps() cannot be declared (internal method)" );
var SubSubAnotherFoo = AnotherFoo.extend(),
SubSubObj1 = new SubSubAnotherFoo(),
SubSubObj2 = new SubSubAnotherFoo();
// to ensure the effect is recursive
assert.ok(
( ( SubSubObj1.arr !== SubSubObj2.arr )
&& ( SubSubObj1.obj !== SubSubObj2.obj )
),
"Instances of subtypes do not share property references"
);
}
var sub_instance = new SubFoo();
assert.ok(
( sub_instance instanceof Foo ),
"Subtypes are considered to be instances of their supertypes " +
"(via instanceof operator)"
);
assert.ok(
sub_instance.isInstanceOf( SubFoo ),
"Subtypes are considered to be instances of their supertypes (via " +
"isInstanceOf method)"
);
// Foo
// |
// SubFoo
// / \
// SubSubFoo SubSubFoo2
//
var SubSubFoo = SubFoo.extend(),
SubSubFoo2 = SubFoo.extend(),
sub_sub_instance = new SubSubFoo(),
sub_sub2_instance = new SubSubFoo2();
assert.ok(
( ( sub_sub_instance instanceof Foo )
&& sub_sub_instance.isInstanceOf( Foo )
),
"Sub-subtypes should be instances of their super-supertype"
);
assert.ok(
( !( sub_instance instanceof SubSubFoo )
&& !( sub_instance.isInstanceOf( SubSubFoo ) )
),
"Supertypes should not be considered instances of their subtypes"
);
assert.ok(
( !( sub_sub2_instance instanceof SubSubFoo )
&& !( sub_sub2_instance.isInstanceOf( SubSubFoo ) )
),
"Subtypes should not be considered instances of their siblings"
);
// to test inheritance of classes that were not previously created via the
// Class.extend() method
var OtherClass = function() {};
OtherClass.prototype =
( function testInvokingClassModuleRequiresObjectAsArgumentIfCreating()
{
foo: 'bla',
};
var SubOther = Class.extend( OtherClass,
{
newFoo: 2,
});
assert.equal(
SubOther.prototype.foo,
OtherClass.prototype.foo,
"Prototype of existing class should be copied to subclass"
);
assert.notEqual(
SubOther().newFoo,
undefined,
"Subtype should contain extended members"
);
assert.throws( function()
{
Class.extend( OtherClass,
{
foo: function() {},
});
}, TypeError, "Cannot override property with a method" );
var AnotherFoo = Class.extend(
{
arr: [],
obj: {},
});
var Obj1 = new AnotherFoo(),
Obj2 = new AnotherFoo();
Obj1.arr.push( 'one' );
Obj2.arr.push( 'two' );
Obj1.obj.a = true;
Obj2.obj.b = true;
// 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
// that we're looking to ensure)
assert.ok(
( ( Obj1.arr[ 0 ] === 'one' ) && ( Obj2.arr[ 0 ] === 'two' ) ),
"Multiple instances of the same class do not share array references"
);
assert.ok(
( ( ( Obj1.obj.a === true ) && ( Obj1.obj.b === undefined ) )
&& ( ( Obj2.obj.a === undefined ) && ( Obj2.obj.b === true ) )
),
"Multiple instances of the same class do not share object references"
);
var arr_val = 1;
var SubAnotherFoo = AnotherFoo.extend(
{
arr: [ arr_val ],
});
var SubObj1 = new SubAnotherFoo(),
SubObj2 = new SubAnotherFoo();
assert.ok(
( ( SubObj1.arr !== SubObj2.arr ) && ( SubObj1.obj !== SubObj2.obj ) ),
"Instances of subtypes do not share property references"
);
assert.ok(
( ( SubObj1.arr[ 0 ] === arr_val ) && ( SubObj2.arr[ 0 ] === arr_val ) ),
"Subtypes can override parent property values"
);
assert.throws( function()
{
Class.extend(
{
__initProps: function() {},
});
}, Error, "__initProps() cannot be declared (internal method)" );
var SubSubAnotherFoo = AnotherFoo.extend(),
SubSubObj1 = new SubSubAnotherFoo(),
SubSubObj2 = new SubSubAnotherFoo();
// to ensure the effect is recursive
assert.ok(
( ( SubSubObj1.arr !== SubSubObj2.arr )
&& ( SubSubObj1.obj !== SubSubObj2.obj )
),
"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"
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': [],
} ),
Interface( {
'abstract method': [],
} )
];
var BaseType;
for ( var i = 0; i < base_types.length; i++ )
{
'abstract method': [],
});
BaseType = base_types[ i ];
assert.ok(
( BaseType.prototype.method instanceof Function ),
"Interface contains defined abstract methods"
);
assert.ok(
( BaseType.prototype.method instanceof Function ),
"Interface contains defined abstract methods"
);
var SubType = Interface.extend( BaseType,
var SubType = Interface.extend( BaseType,
{
'abstract second': [],
});
assert.ok(
( SubType.prototype instanceof BaseType ),
"Generic interface extend method can extend from other interfaces"
);
assert.ok(
( SubType.prototype.method === BaseType.prototype.method ),
"Interface subtypes inherit abstract methods"
);
assert.ok(
( SubType.prototype.second instanceof Function ),
"Interfaces can be extended with additional abstract methods"
);
assert.ok(
( BaseType.extend instanceof Function ),
"Interface contains extend method"
);
var SubType2 = BaseType.extend(
{
'abstract second': [],
});
assert.ok(
( SubType2.prototype instanceof BaseType ),
"Interface extend method can extend interfaces"
);
assert.ok(
( SubType2.prototype.second instanceof Function ),
"Interfaces can be extended with additional abstract methods using " +
"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()
{
'abstract second': [],
});
assert.throws( function()
{
Interface( 'moo' );
Interface( 5 );
Interface( false );
Interface();
},
TypeError,
"Invoking interface module requires object as argument if extending " +
"from base interface"
);
assert.ok(
( SubType.prototype instanceof BaseType ),
"Generic interface extend method can extend from other interfaces"
);
var args = [ {}, 'one', 'two', 'three' ];
assert.ok(
( SubType.prototype.method === BaseType.prototype.method ),
"Interface subtypes inherit abstract methods"
);
// we must only provide one argument if the first argument is an object (the
// interface definition)
try
{
Interface.apply( null, args );
assert.ok(
( SubType.prototype.second instanceof Function ),
"Interfaces can be extended with additional abstract methods"
);
assert.ok(
( BaseType.extend instanceof Function ),
"Interface contains extend method"
);
var SubType2 = BaseType.extend(
{
'abstract second': [],
});
assert.ok(
( SubType2.prototype instanceof BaseType ),
"Interface extend method can extend interfaces"
);
assert.ok(
( SubType2.prototype.second instanceof Function ),
"Interfaces can be extended with additional abstract methods using " +
"shorthand extend method"
);
// 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
echo "/** $module **/"
echo "( function( exports )"
echo "( function( module )"
echo "{"
echo " var exports = module.exports = {};"
# add the module, removing trailing commas
cat $filename | $RMTRAIL
echo "} )( exports['$module'] = {} );"
echo "} )( module['$module'] = {} );"
done
# include tests?

View File

@ -18,12 +18,12 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
exports.common = {
module.common = { exports: {
require: function ( id )
{
return require( id );
}
};
} };
function failAssertion( err )
@ -37,7 +37,7 @@ function failAssertion( err )
*
* This contains only the used assertions
*/
exports.assert = {
module.assert = { exports: {
equal: function ( val, cmp, err )
{
if ( val !== cmp )
@ -101,5 +101,5 @@ exports.assert = {
}
}
},
};
} };

View File

@ -51,7 +51,7 @@ var easejs = {};
*
* @type {Object.<string,Object>}
*/
var exports = {};
var module = {};
/**
* Returns the requested module
@ -71,19 +71,19 @@ var easejs = {};
var id_clean = module_id.replace( /^.\//, '' );
// attempt to retrieve the module
var module = exports[ id_clean ];
if ( module === undefined )
var mod = module[ id_clean ];
if ( mod === undefined )
{
throw "[ease.js] Undefined module: " + module_id;
}
return module;
return mod.exports;
};
/**{CONTENT}**/
// the following should match the exports of /index.js
ns_exports.Class = exports['class'];
ns_exports.Interface = exports['interface'];
ns_exports.Class = module['class'].exports;
ns_exports.Interface = module['interface'].exports;
} )( easejs );