diff --git a/test/Class/ConstructorTest.js b/test/Class/ConstructorTest.js new file mode 100644 index 0000000..605a54c --- /dev/null +++ b/test/Class/ConstructorTest.js @@ -0,0 +1,203 @@ +/** + * Tests class module constructor creation + * + * Copyright (C) 2010, 2011, 2012, 2013 Mike Gerwitz + * + * This file is part of GNU ease.js. + * + * ease.js is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +require( 'common' ).testCase( +{ + setUp: function() + { + this.Sut = this.require( 'class' ); + }, + + + /** + * As a sanity check, ensure that the constructor is not invoked upon + * defining the class. (Note that the case of ensuring that it is not + * called when creating a subtype is handled by the ExtendTest case.) + */ + 'Constructor should not be invoked before instantiation': function() + { + var called = false; + this.Sut.extend( { __construct: function() { called = true; } } ); + + this.assertNotEqual( called, true ); + }, + + + /** + * Since __construct is a special method that is not recognized by + * ECMAScript itself, we must ensure that it is invoked when the class + * is instantiated. Further, it should only be called a single time, + * which is particularly important if it produces side-effects. + */ + 'Constructor should be invoked once upon instantiation': function() + { + var called = 0; + + var Foo = this.Sut.extend( + { + __construct: function() { called++; } + } ); + + // note that we're not yet testing the more consise new-less + // invocation style + new Foo(); + this.assertEqual( called, 1 ); + }, + + + /** + * Once invoked, the __construct method should be bound to the newly + * created instance. + */ + 'Constructor should be invoked within context of new instance': + function() + { + var expected = Math.random(); + + var Foo = this.Sut.extend( + { + val: null, + __construct: function() { this.val = expected; } + } ); + + // if `this' was bound to the instance, then __construct should set + // VAL to EXPECTED + var inst = new Foo(); + this.assertEqual( inst.val, expected ); + }, + + + /** + * All arguments passed to the constructor (that is, by invoking the + * ``class'') should be passed to __construct, unchanged and + * uncopied---that is, references should be retained. + */ + 'Constructor arguments should be passed unchanged to __construct': + function() + { + var args = [ "foo", { bar: 'baz' }, [ 'moo', 'cow' ] ], + given = null; + + var Foo = this.Sut.extend( + { + __construct: function() + { + given = Array.prototype.slice.call( arguments, 0 ); + } + } ); + + new Foo( args[ 0 ], args[ 1 ], args[ 2 ] ); + + // make sure we have everything and didn't get anything extra + this.assertEqual( given.length, args.length ); + + var i = args.length; + while ( i-- ) + { + this.assertStrictEqual( given[ i ], args[ i ], + "Ctor argument mismatch: " + i + ); + } + }, + + + /** + * If a subtype does not define its own constructor, then its parent's + * should be called by default. Note that this behavior---as is clear by + * the name __construct---is modelled after PHP; Java classes, for + * instance, do not inherit their parents' constructors. + */ + 'Parent constructor should be invoked for subtype if not overridden': + function() + { + var called = false; + + var Sub = this.Sut.extend( + { + __construct: function() { called = true; } + } ).extend( {} ); + + new Sub(); + this.assertOk( called ); + }, + + + /** + * Classes created through ease.js do not require use of the `new' + * keyword, which allows for a much more natural, concise, and less + * error-prone syntax. Ensure that a new instance is created even when + * it is omitted. + * + * The rest of the tests above would then stand, since they use the + * `new' keyword and this concise format has no choice but to ultimately + * do the same; otherwise, it would not be recognized by instanceof. + */ + 'Constructor does not require `new\' keyword': function() + { + var Foo = this.Sut.extend( {} ); + + this.assertOk( new Foo() instanceof Foo ); // sanity check + this.assertOk( Foo() instanceof Foo ); + }, + + + + /** + * In certain OO languages, one would prevent a class from being + * instantiated by declaring the constructor as protected or private. To + * me (Mike Gerwitz), this is cryptic. A better method would simply be + * to throw an exception. Perhaps, in the future, an alternative will be + * provided for consistency. + * + * The constructor must be public. (It is for this reason that you will + * often see the convention of omitting visibility keywords entirely for + * __construct, since public is the default and there is no other + * option.) + */ + '__construct must be public': function() + { + var Sut = this.Sut; + + this.assertThrows( function() + { + Sut( { 'protected __construct': function() {} } ); + }, TypeError, "Constructor should not be able to be protected" ); + + this.assertThrows( function() + { + Sut( { 'private __construct': function() {} } ); + }, TypeError, "Constructor should not be able to be private" ); + }, + + + /** + * When a constructor is instantiated conventionally in ECMAScript, the + * instance's `constructor' property is set to the constructor that was + * used to instantiate it. The same should be true for class instances. + * + * This will also be important for reflection. + */ + '`constructor\' property is properly set to class object': function() + { + var Foo = this.Sut.extend( {} ); + this.assertStrictEqual( Foo().constructor, Foo ); + }, +} ); diff --git a/test/test-class-constructor.js b/test/test-class-constructor.js deleted file mode 100644 index a35423a..0000000 --- a/test/test-class-constructor.js +++ /dev/null @@ -1,184 +0,0 @@ -/** - * Tests class module constructor creation - * - * Copyright (C) 2010, 2011, 2012, 2013 Mike Gerwitz - * - * This file is part of GNU ease.js. - * - * ease.js is free software: you can redistribute it and/or modify - * it under the terms of the GNU 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -var common = require( './common' ), - assert = require( 'assert' ), - Class = common.require( 'class' ); - -// these two variables are declared outside of the class to ensure that they -// will still be set even if the context of the constructor is wrong -var construct_count = 0, - construct_context = null, - construct_args = null, - -// create a basic test class - Foo = Class.extend( - { - __construct: function() - { - construct_count++; - construct_context = this; - construct_args = arguments; - }, - }) -; - - -assert.ok( - ( Foo.prototype.__construct instanceof Function ), - "Provided properties should be copied to the new class prototype" -); - -assert.equal( - construct_count, - 0, - "Constructor should not be called before class is instantiated" -); - -var args = [ 'foo', 'bar' ], - obj = new Foo( args[0], args[1] ); - -assert.equal( - construct_count, - 1, - "Constructor should be invoked once the class is instantiated" -); - -assert.equal( - construct_context.__iid, - obj.__iid, - "Constructor should be invoked within the context of the class instance" -); - -assert.notEqual( - construct_args, - null, - "Constructor arguments should be passed to the constructor" -); - -assert.equal( - construct_args.length, - args.length, - "All arguments should be passed to the constructor" -); - -// check the argument values -for ( var i = 0, len = args.length; i < len; i++ ) -{ - assert.equal( - construct_args[ i ], - args[ i ], - "Arguments should be passed to the constructor: " + i - ); -} - -var SubFoo = Foo.extend( -{ - args: [ 'should', 'be', 'overwritten' ], -} ); - -construct_count = 0; -construct_context = null; - -var args2 = [ 'fried', 'pickle' ], - subobj = new SubFoo( args2[ 0 ], args2[ 1 ] ); - -assert.equal( - construct_count, - 1, - "Parent constructor should be called for subtype if not overridden" -); - -assert.equal( - construct_context.__iid, - subobj.__iid, - "Parent constructor is run in context of the subtype" -); - -// this should be implied by the previous test, but let's add it for some peace -// of mind -assert.ok( - ( ( construct_args[ 0 ] === args2[ 0 ] ) - && ( construct_args[ 1 ] == args2[ 1 ] ) - ), - "Parent constructor sets values on subtype" -); - - -var subobj2 = SubFoo( args2[ 0 ], args2[ 1 ] ); - -assert.ok( - ( subobj2 instanceof SubFoo ), - "Constructor is self-invoking" -); - -assert.equal( - construct_context.__iid, - subobj2.__iid, - "Self-invoking constructor is run in the context of the new object" -); - -assert.ok( - ( ( construct_args[ 0 ] === args2[ 0 ] ) - && ( construct_args[ 1 ] == args2[ 1 ] ) - ), - "Self-invoking constructor receives arguments" -); - - -/** - * In PHP, one would prevent a class from being instantiated by declaring the - * constructor as protected or private. To me, this is cryptic. A better method - * would simply be to throw an exception. Perhaps, in the future, an alternative - * will be provided for consistency. - * - * The constructor must be public. - */ -( function testConstructorCannotBeDeclaredAsProtectedOrPrivate() -{ - assert['throws']( function() - { - Class( { 'protected __construct': function() {} } ); - }, TypeError, "Constructor cannot be protected" ); - - assert['throws']( function() - { - Class( { 'private __construct': function() {} } ); - }, TypeError, "Constructor cannot be private" ); -} )(); - - -/** - * When a constructor is instantiated, the instance's 'constructor' property is - * set to the constructor that was used to instantiate it. The same should be - * true for class instances. - * - * This will also be important for reflection. - */ -( function testConsructorPropertyIsProperlySetToClass() -{ - var Foo = Class( {} ); - - assert.ok( Foo().constructor === Foo, - "Instance constructor should be set to class" - ); -} )(); -