diff --git a/test/FallbackVisibilityObjectFactoryTest.js b/test/FallbackVisibilityObjectFactoryTest.js index 945ef74..16d0bf6 100644 --- a/test/FallbackVisibilityObjectFactoryTest.js +++ b/test/FallbackVisibilityObjectFactoryTest.js @@ -21,91 +21,90 @@ * @author Mike Gerwitz */ - -var common = require( './common' ), - assert = require( 'assert' ), - - // SUT - FallbackVisibilityObjectFactory = - common.require( 'FallbackVisibilityObjectFactory' ), - - // parent of SUT - VisibilityObjectFactory = common.require( 'VisibilityObjectFactory' ), - - sut = FallbackVisibilityObjectFactory(), - - props = methods = { - 'public': {}, - 'protected': {}, - 'private': {}, - } -; - - -/** - * To keep with the spirit of ease.js, we should be able to instantiate - * VisibilityObjectFactory both with and without the 'new' keyword - * - * Consistency is key with these sorts of things. - */ -( function testCanInstantiateWithAndWithoutNewKeyword() +require( 'common' ).testCase( { - // with 'new' keyword - assert.ok( - ( new FallbackVisibilityObjectFactory() ) - instanceof FallbackVisibilityObjectFactory, - "Should be able to instantiate FallbackVisibilityObjectFactory with " + - "'new' keyword" - ); + caseSetUp: function() + { + this.Sut = this.require( 'FallbackVisibilityObjectFactory' ); - // without 'new' keyword - assert.ok( - FallbackVisibilityObjectFactory() - instanceof FallbackVisibilityObjectFactory, - "Should be able to instantiate FallbackVisibilityObjectFactory " + - "without 'new' keyword" - ); -} )(); + // parent of SUT + this.VisibilityObjectFactory = + this.require( 'VisibilityObjectFactory' ); + + this.props = this.methods = { + 'public': {}, + 'protected': {}, + 'private': {}, + }; + }, -/** - * VisibilityObjectFactory should be part of our prototype chain. - */ -( function testInheritsFromVisibilityObjectFactory() -{ - // check an instance, rather than __proto__, because older engines do not - // support it - assert.ok( - FallbackVisibilityObjectFactory() instanceof VisibilityObjectFactory, - "Fallback should inherit from VisibilityObjectFactory" - ); -} )(); + /** + * To keep with the spirit of ease.js, we should be able to instantiate + * VisibilityObjectFactory both with and without the 'new' keyword + * + * Consistency is key with these sorts of things. + */ + 'Can instantiate with and without `new` keyword': function() + { + // with 'new' keyword + this.assertOk( + ( new this.Sut() ) instanceof this.Sut, + "Should be able to instantiate FallbackVisibilityObjectFactory " + + "with 'new' keyword" + ); + + // without 'new' keyword + this.assertOk( + this.Sut() instanceof this.Sut, + "Should be able to instantiate FallbackVisibilityObjectFactory " + + "without 'new' keyword" + ); + }, -/** - * We're falling back because we do not support the private visibility layer (or - * any layers, for that matter). Ensure it's not created. - */ -( function testSetupMethodShouldNotAddPrivateLayer() -{ - var dest = {}, - obj = sut.setup( dest, props, methods ); - - assert.strictEqual( dest, obj, - "Private visibility layer is not added atop destination" - ); -} )(); + /** + * VisibilityObjectFactory should be part of our prototype chain. + */ + 'Inherits from visibility object factory': function() + { + // check an instance, rather than __proto__, because older engines do + // not support it + this.assertOk( + this.Sut() instanceof this.VisibilityObjectFactory, + "Fallback should inherit from VisibilityObjectFactory" + ); + }, -( function testCreatingPropertyProxyShouldSimplyReturnSelf() -{ - var base = {}, - dest = {}; + /** + * We're falling back because we do not support the private visibility layer + * (or any layers, for that matter). Ensure it's not created. + */ + 'Setup method should not add private layer': function() + { + var dest = {}, + obj = this.Sut().setup( dest, this.props, this.methods ); - assert.strictEqual( - sut.createPropProxy( base, dest, props ), - base, - "Creating property proxy should simply return original object" - ); -} )(); + this.assertStrictEqual( dest, obj, + "Private visibility layer is not added atop destination" + ); + }, + + + /** + * Getters/setters are unsupported (thus the fallback). + */ + 'Creating property proxy should simply return self': function() + { + var base = {}, + dest = {}; + + this.assertStrictEqual( + this.Sut().createPropProxy( base, dest, this.props ), + base, + "Creating property proxy should simply return original object" + ); + }, +} ); diff --git a/test/MethodWrapperFactoryTest.js b/test/MethodWrapperFactoryTest.js index 27d5a2c..b74d6bd 100644 --- a/test/MethodWrapperFactoryTest.js +++ b/test/MethodWrapperFactoryTest.js @@ -22,96 +22,97 @@ */ -var common = require( './common' ), - assert = require( 'assert' ), - - Sut = common.require( 'MethodWrapperFactory' ) -; - - -/** - * To keep with the spirit of ease.js, we should be able to instantiate - * MethodWrapperFactory both with and without the 'new' keyword - * - * Consistency is key with these sorts of things. - */ -( function testCanInstantiateWithAndWithoutNewKeyword() +require( 'common' ).testCase( { - // with 'new' keyword - assert.ok( - ( new Sut() ) - instanceof Sut, - "Should be able to instantiate MethodWrapperFactory with " + - "'new' keyword" - ); - - // without 'new' keyword - assert.ok( ( Sut() instanceof Sut ), - "Should be able to instantiate MethodWrapperFactory " + - "without 'new' keyword" - ); -} )(); + caseSetUp: function() + { + this.Sut = this.require( 'MethodWrapperFactory' ); + }, -/** - * The factory itself is rather simple. The class should accept a factory - * function which should return the wrapped method. - */ -( function testProvidedFactoryFunctionIsProperlyCalled() -{ - var called = false, - method = function() {}, - super_method = function() {}, - cid = 55, - getInst = function() {}, - name = 'someMethod', - keywords = { 'static': true, 'public': true }, - retval = 'foobar'; + /** + * To keep with the spirit of ease.js, we should be able to instantiate + * MethodWrapperFactory both with and without the 'new' keyword + * + * Consistency is key with these sorts of things. + */ + 'Can instantiate with and without new keyword': function() + { + // with 'new' keyword + this.assertOk( + ( new this.Sut() ) instanceof this.Sut, + "Should be able to instantiate MethodWrapperFactory with " + + "'new' keyword" + ); - var result = Sut( - function( - given_method, given_super, given_cid, givenGetInst, given_name, - given_keywords - ) - { - called = true; + // without 'new' keyword + this.assertOk( ( this.Sut() instanceof this.Sut ), + "Should be able to instantiate MethodWrapperFactory " + + "without 'new' keyword" + ); + }, - assert.equal( given_method, method, - "Factory method should be provided with method to wrap" - ); - assert.equal( given_super, super_method, - "Factory method should be provided with super method" - ); + /** + * The factory itself is rather simple. The class should accept a factory + * function which should return the wrapped method. + */ + 'Provided factory function is properly called': function() + { + var _self = this, + called = false, + method = function() {}, + super_method = function() {}, + cid = 55, + getInst = function() {}, + name = 'someMethod', + keywords = { 'static': true, 'public': true }, + retval = 'foobar'; - assert.equal( given_cid, cid, - "Factory method should be provided with cid" - ); + var result = this.Sut( + function( + given_method, given_super, given_cid, givenGetInst, given_name, + given_keywords + ) + { + called = true; - assert.equal( givenGetInst, getInst, - "Factory method should be provided with proper inst function" - ); + _self.assertEqual( given_method, method, + "Factory method should be provided with method to wrap" + ); - assert.equal( given_name, name, - "Factory method should be provided with proper method name" - ); + _self.assertEqual( given_super, super_method, + "Factory method should be provided with super method" + ); - assert.equal( given_keywords, keywords, - "Factory method should be provided with proper keywords" - ); + _self.assertEqual( given_cid, cid, + "Factory method should be provided with cid" + ); - return retval; - } - ).wrapMethod( method, super_method, cid, getInst, name, keywords ); + _self.assertEqual( givenGetInst, getInst, + "Factory method should be provided with proper inst function" + ); - // we'll include this in addition to the following assertion (which is - // redundant) to make debugging more clear - assert.equal( called, true, - "Given factory method should be called" - ); + _self.assertEqual( given_name, name, + "Factory method should be provided with proper method name" + ); - assert.equal( result, retval, - "Should return value from factory function" - ); -} )(); + _self.assertEqual( given_keywords, keywords, + "Factory method should be provided with proper keywords" + ); + return retval; + } + ).wrapMethod( method, super_method, cid, getInst, name, keywords ); + + // we'll include this in addition to the following assertion (which is + // redundant) to make debugging more clear + this.assertEqual( called, true, + "Given factory method should be called" + ); + + this.assertEqual( result, retval, + "Should return value from factory function" + ); + }, +} ); diff --git a/test/MethodWrappersTest.js b/test/MethodWrappersTest.js index ba481ed..543ecba 100644 --- a/test/MethodWrappersTest.js +++ b/test/MethodWrappersTest.js @@ -21,363 +21,375 @@ * @author Mike Gerwitz */ -var common = require( './common' ), - assert = require( 'assert' ), - util = common.require( 'util' ), - sut = common.require( 'MethodWrappers' ) -; - - -/** - * The wrappers accept a function that should return the instance to be bound to - * 'this' when invoking a method. This has some important consequences, such as - * the ability to implement protected/private members. - */ -( function testMethodInvocationBindsThisToPassedInstance() +require( 'common' ).testCase( { - var instance = function() {}, - val = 'fooboo', - val2 = 'fooboo2', - iid = 1, - called = false, - - getInst = function() + caseSetUp: function() + { + // common assertions between a couple of proxy tests + this.proxyErrorAssertCommon = function( e, prop, method ) { - called = true; - return instance; - }, + this.assertOk( + e.message.search( 'Unable to proxy' ) > -1, + "Unexpected error received: " + e.message + ); - method = sut.standard.wrapNew( - function() + this.assertOk( + ( ( e.message.search( prop ) > -1 ) + && ( e.message.search( method ) > -1 ) + ), + "Error should contain property and method names" + ); + }; + }, + + + setUp: function() + { + this._sut = this.require( 'MethodWrappers' ); + }, + + + /** + * The wrappers accept a function that should return the instance to be + * bound to 'this' when invoking a method. This has some important + * consequences, such as the ability to implement protected/private members. + */ + 'Method invocation binds `this` to passed instance': function() + { + var instance = function() {}, + val = 'fooboo', + val2 = 'fooboo2', + iid = 1, + called = false, + + getInst = function() { - return this.foo; + called = true; + return instance; }, - null, 0, getInst - ), - override = sut.standard.wrapOverride( - function() + method = this._sut.standard.wrapNew( + function() + { + return this.foo; + }, + null, 0, getInst + ), + + override = this._sut.standard.wrapOverride( + function() + { + return this.foo2; + }, + method, 0, getInst + ) + ; + + // set instance values + instance.foo = val; + instance.foo2 = val2; + + this.assertEqual( method(), val, + "Calling method will bind 'this' to passed instance" + ); + + this.assertEqual( override(), val2, + "Calling method override will bind 'this' to passed instance" + ); + }, + + + /** + * The __super property is defined for method overrides and permits invoking + * the overridden method (method of the supertype). + * + * In this test, we are not looking to assert that __super matches the super + * method. Rather, we want to ensure it /invokes/ it. This is because the + * super method may be wrapped to provide additional functionality. We don't + * know, we don't care. We just want to make sure it's functioning properly. + */ + 'Overriden method should contain reference to super method': function() + { + var _self = this, + orig_called = false, + getInst = function() {}, + + // "super" method + method = this._sut.standard.wrapNew( + function() + { + orig_called = true; + }, + null, 0, getInst + ), + + // override method + override = this._sut.standard.wrapOverride( + function() + { + _self.assertNotEqual( + this.__super, + undefined, + "__super is defined for overridden method" + ); + + this.__super(); + _self.assertEqual( + orig_called, + true, + "Invoking __super calls super method" + ); + }, + method, 0, getInst + ) + ; + + // invoke the method to run the above assertions + override(); + }, + + + /** + * If the method is called when bound to a different context (e.g. for + * protected/private members), __super may not be properly bound. + * + * This test is in response to a bug found after implementing visibility + * support. The __super() method was previously defined on 'this', which may + * or may not be the context that is actually used. Likely, it's not. + */ + 'Super method works properly when context differs': function() + { + var super_called = false, + retobj = {}, + + getInst = function() { - return this.foo2; + return retobj; }, - method, 0, getInst - ) - ; - // set instance values - instance.foo = val; - instance.foo2 = val2; + // super method to be overridden + method = this._sut.standard.wrapNew( + function() + { + super_called = true; + }, + null, 0, getInst + ), - assert.equal( method(), val, - "Calling method will bind 'this' to passed instance" - ); + // the overriding method + override = this._sut.standard.wrapOverride( + function() + { + this.__super(); + }, + method, 0, getInst + ) + ; - assert.equal( override(), val2, - "Calling method override will bind 'this' to passed instance" - ); -} )(); + // call the overriding method + override(); + + // ensure that the super method was called + this.assertEqual( super_called, true, + "__super() method is called even when context differs" + ); + + // finally, ensure that __super is no longer set on the returned object + // after the call to ensure that the caller cannot break encapsulation + // by stealing a method reference (sneaky, sneaky) + this.assertEqual( retobj.__super, undefined, + "__super() method is unset after being called" + ); + }, -/** - * The __super property is defined for method overrides and permits invoking the - * overridden method (method of the supertype). - * - * In this test, we are not looking to assert that __super matches the super - * method. Rather, we want to ensure it /invokes/ it. This is because the super - * method may be wrapped to provide additional functionality. We don't know, we - * don't care. We just want to make sure it's functioning properly. - */ -( function testOverridenMethodShouldContainReferenceToSuperMethod() -{ - var orig_called = false, - getInst = function() {}, + /** + * The proxy wrapper should forward all arguments to the provided object's + * appropriate method. The return value should also be proxied back to the + * caller. + */ + 'Proxy will properly forward calls to destination object': function() + { + var name = 'someMethod', + propname = 'dest', - // "super" method - method = sut.standard.wrapNew( - function() - { - orig_called = true; - }, - null, 0, getInst - ), + args = [ 1, {}, 'three' ], + args_given = [], - // override method - override = sut.standard.wrapOverride( - function() - { - assert.notEqual( - this.__super, - undefined, - "__super is defined for overridden method" - ); - - this.__super(); - assert.equal( - orig_called, - true, - "Invoking __super calls super method" - ); - }, - method, 0, getInst - ) - ; - - // invoke the method to run the above assertions - override(); -} )(); - - -/** - * If the method is called when bound to a different context (e.g. for - * protected/private members), __super may not be properly bound. - * - * This test is in response to a bug found after implementing visibility - * support. The __super() method was previously defined on 'this', which may or - * may not be the context that is actually used. Likely, it's not. - */ -( function testSuperMethodWorksProperlyWhenContextDiffers() -{ - var super_called = false, - retobj = {}, - - getInst = function() - { - return retobj; - }, - - // super method to be overridden - method = sut.standard.wrapNew( - function() - { - super_called = true; - }, - null, 0, getInst - ), - - // the overriding method - override = sut.standard.wrapOverride( - function() - { - this.__super(); - }, - method, 0, getInst - ) - ; - - // call the overriding method - override(); - - // ensure that the super method was called - assert.equal( super_called, true, - "__super() method is called even when context differs" - ); - - // finally, ensure that __super is no longer set on the returned object - // after the call to ensure that the caller cannot break encapsulation by - // stealing a method reference (sneaky, sneaky) - assert.equal( retobj.__super, undefined, - "__super() method is unset after being called" - ); -} )(); - - -/** - * The proxy wrapper should forward all arguments to the provided object's - * appropriate method. The return value should also be proxied back to the - * caller. - */ -( function testProxyWillProperlyForwardCallToDestinationObject() -{ - var name = 'someMethod', - propname = 'dest', - - args = [ 1, {}, 'three' ], - args_given = [], - - getInst = function() - { - return inst; - }, - - method_retval = {}, - dest = { - someMethod: function() - { - args_given = Array.prototype.slice.call( arguments ); - return method_retval; - }, - }, - - // acts like a class instance - inst = { dest: dest }, - - proxy = sut.standard.wrapProxy( propname, null, 0, getInst, name ) - ; - - assert.strictEqual( method_retval, proxy.apply( inst, args ), - "Proxy call should return the value from the destination" - ); - - assert.deepEqual( args, args_given, - "All arguments should be properly forwarded to the destination" - ); -} )(); - - -/** - * If the destination object returns itself, then we should return the context - * in which the proxy was called; this ensures that we do not break - * encapsulation. Consequently, it also provides a more consistent and sensical - * API and permits method chaining. - * - * If this is not the desired result, then the user is free to forefit the proxy - * wrapper and instead use a normal method, manually proxying the call. - */ -( function testProxyReturnValueIsReplacedWithContextIfDestinationReturnsSelf() -{ - var propname = 'foo', - method = 'bar', - - foo = { - bar: function() - { - // return "self" - return foo; - } - }, - - inst = { foo: foo }, - - ret = sut.standard.wrapProxy( - propname, null, 0, - function() + getInst = function() { return inst; }, - method - ).call( inst ) - ; - assert.strictEqual( inst, ret, - "Proxy should return instance in place of destination, if returned" - ); -} )(); + method_retval = {}, + dest = { + someMethod: function() + { + args_given = Array.prototype.slice.call( arguments ); + return method_retval; + }, + }, + + // acts like a class instance + inst = { dest: dest }, + + proxy = this._sut.standard.wrapProxy( + propname, null, 0, getInst, name + ) + ; + + this.assertStrictEqual( method_retval, proxy.apply( inst, args ), + "Proxy call should return the value from the destination" + ); + + this.assertDeepEqual( args, args_given, + "All arguments should be properly forwarded to the destination" + ); + }, -// common assertions between a couple of proxy tests -function proxyErrorAssertCommon( e, prop, method ) -{ - assert.ok( - e.message.search( 'Unable to proxy' ) > -1, - "Unexpected error received: " + e.message - ); - - assert.ok( - ( ( e.message.search( prop ) > -1 ) - && ( e.message.search( method ) > -1 ) - ), - "Error should contain property and method names" - ); -} - - -/** - * Rather than allowing a cryptic error to be thrown by the engine, take some - * initiative and attempt to detect when a call will fail due to the destination - * not being an object. - */ -( function testProxyThrowsErrorIfCallWillFailDueToNonObject() -{ - var prop = 'noexist', - method = 'foo'; - - try + /** + * If the destination object returns itself, then we should return the + * context in which the proxy was called; this ensures that we do not break + * encapsulation. Consequently, it also provides a more consistent and + * sensical API and permits method chaining. + * + * If this is not the desired result, then the user is free to forefit the + * proxy wrapper and instead use a normal method, manually proxying the + * call. + */ + 'Proxy retval is replaced with context if dest returns self': function() { - // should fail because 'noexist' does not exist on the object - sut.standard.wrapProxy( - prop, null, 0, - function() { return {}; }, - method - )(); - } - catch ( e ) + var propname = 'foo', + method = 'bar', + + foo = { + bar: function() + { + // return "self" + return foo; + } + }, + + inst = { foo: foo }, + + ret = this._sut.standard.wrapProxy( + propname, null, 0, + function() + { + return inst; + }, + method + ).call( inst ) + ; + + this.assertStrictEqual( inst, ret, + "Proxy should return instance in place of destination, if returned" + ); + }, + + + /** + * Rather than allowing a cryptic error to be thrown by the engine, take + * some initiative and attempt to detect when a call will fail due to the + * destination not being an object. + */ + 'Proxy throws error if call will faill due to non-object': function() { - proxyErrorAssertCommon( e, prop, method ); - return; - } + var prop = 'noexist', + method = 'foo'; - assert.fail( - "Error should be thrown if proxy would fail due to a non-object" - ); -} )(); - - -/** - * Rather than allowing a cryptic error to be thrown by the engine, take some - * initiative and attempt to detect when a call will fail due to the destination - * method not being a function. - */ -( function testProxyThrowsErrorIfCallWillFailDueToNonObject() -{ - var prop = 'dest', - method = 'foo'; - - try - { - // should fail because 'noexist' does not exist on the object - sut.standard.wrapProxy( - prop, null, 0, - function() { return { dest: { foo: 'notafunc' } }; }, - method - )(); - } - catch ( e ) - { - proxyErrorAssertCommon( e, prop, method ); - return; - } - - assert.fail( - "Error should be thrown if proxy would fail due to a non-function" - ); -} )(); - - -/** - * If the `static' keyword is provided, then the proxy mustn't operate on - * instance properties. Instead, the static accessor method $() must be used. - */ -( function testCanProxyToStaticMembers() -{ - var getInst = function() + try { - // pretend that we're a static class with a static accessor method - return { - $: function( name ) - { - // implicitly tests that the argument is properly passed - // (would otherwise return `undefined`) - return s[ name ]; - }, + // should fail because 'noexist' does not exist on the object + this._sut.standard.wrapProxy( + prop, null, 0, + function() { return {}; }, + method + )(); + } + catch ( e ) + { + this.proxyErrorAssertCommon( e, prop, method ); + return; + } + + this.assertFail( + "Error should be thrown if proxy would fail due to a non-object" + ); + }, + + + /** + * Rather than allowing a cryptic error to be thrown by the engine, take + * some initiative and attempt to detect when a call will fail due to the + * destination method not being a function. + */ + 'Proxy throws error if call will fail due to non-function': function() + { + var prop = 'dest', + method = 'foo'; + + try + { + // should fail because 'noexist' does not exist on the object + this._sut.standard.wrapProxy( + prop, null, 0, + function() { return { dest: { foo: 'notafunc' } }; }, + method + )(); + } + catch ( e ) + { + this.proxyErrorAssertCommon( e, prop, method ); + return; + } + + this.assertFail( + "Error should be thrown if proxy would fail due to a non-function" + ); + }, + + + /** + * If the `static' keyword is provided, then the proxy mustn't operate on + * instance properties. Instead, the static accessor method $() must be + * used. + */ + 'Can proxy to static members': function() + { + var getInst = function() + { + // pretend that we're a static class with a static accessor method + return { + $: function( name ) + { + // implicitly tests that the argument is properly passed + // (would otherwise return `undefined`) + return s[ name ]; + }, + }; + }, + + keywords = { 'static': true }; + + val = [ 'value' ], + s = { + // destination object + foo: { + method: function() + { + return val; + }, + } }; - }, - keywords = { 'static': true }; - - val = [ 'value' ], - s = { - // destination object - foo: { - method: function() - { - return val; - }, - } - }; - - assert.strictEqual( val, - sut.standard.wrapProxy( 'foo', null, 0, getInst, 'method', keywords )(), - "Should properly proxy to static membesr via static accessor method" - ); -} )(); + this.assertStrictEqual( val, + this._sut.standard.wrapProxy( + 'foo', null, 0, getInst, 'method', keywords + )(), + "Should properly proxy to static membesr via static accessor method" + ); + }, +} ); diff --git a/test/VisibilityObjectFactoryFactoryTest.js b/test/VisibilityObjectFactoryFactoryTest.js index bd804b1..afcba59 100644 --- a/test/VisibilityObjectFactoryFactoryTest.js +++ b/test/VisibilityObjectFactoryFactoryTest.js @@ -21,57 +21,62 @@ * @author Mike Gerwitz */ - -var common = require( './common' ), - assert = require( 'assert' ), - util = common.require( 'util' ), - - sut = common.require( 'VisibilityObjectFactoryFactory' ), - - VisibilityObjectFactory = common.require( 'VisibilityObjectFactory' ), - - FallbackVisibilityObjectFactory = - common.require( 'FallbackVisibilityObjectFactory' ) -; - - -/** - * By default, if supported by our environment, we should use the standard - * factory to provide proper visibility support. - */ -( function testReturnsStandardIfNotFallingBack() +require( 'common' ).testCase( { - // don't bother with the test if we don't support the standard visibility - // object - if ( util.definePropertyFallback() ) + caseSetUp: function() { - return; - } + this.sut = this.require( 'VisibilityObjectFactoryFactory' ); - assert.ok( - ( sut.fromEnvironment() instanceof VisibilityObjectFactory ), - "Creates standard VisibilityObjectFactory if supported" - ); -} )(); + this.VisibilityObjectFactory = + this.require( 'VisibilityObjectFactory' ); + + this.FallbackVisibilityObjectFactory = + this.require( 'FallbackVisibilityObjectFactory' ); + + this.util = this.require( 'util' ); + }, -/** - * If not supported by our environment, we should be permitted to fall back to a - * working implementation that sacrifices visibility support. - */ -( function testReturnsFallbackFactoryIfFallingBack() -{ - var old = util.definePropertyFallback(); + /** + * By default, if supported by our environment, we should use the standard + * factory to provide proper visibility support. + */ + 'Returns standard factory if not falling back': function() + { + // don't bother with the test if we don't support the standard visibility + // object + if ( this.util.definePropertyFallback() ) + { + return; + } - // force fallback - util.definePropertyFallback( true ); + this.assertOk( + ( this.sut.fromEnvironment() + instanceof this.VisibilityObjectFactory ), + "Creates standard VisibilityObjectFactory if supported" + ); + }, - assert.ok( - ( sut.fromEnvironment() instanceof FallbackVisibilityObjectFactory ), - "Creates fallback VisibilityObjectFactory if falling back" - ); - // restore fallback - util.definePropertyFallback( old ); -} )(); + /** + * If not supported by our environment, we should be permitted to fall back to a + * working implementation that sacrifices visibility support. + */ + 'Returns fallback factory if falling back': function() + { + var old = this.util.definePropertyFallback(); + // force fallback + this.util.definePropertyFallback( true ); + + this.assertOk( + ( this.sut.fromEnvironment() + instanceof this.FallbackVisibilityObjectFactory + ), + "Creates fallback VisibilityObjectFactory if falling back" + ); + + // restore fallback + this.util.definePropertyFallback( old ); + }, +} ); diff --git a/test/VisibilityObjectFactoryTest.js b/test/VisibilityObjectFactoryTest.js index 864634a..57288c0 100644 --- a/test/VisibilityObjectFactoryTest.js +++ b/test/VisibilityObjectFactoryTest.js @@ -21,270 +21,277 @@ * @author Mike Gerwitz */ - -var common = require( './common' ), - assert = require( 'assert' ); - -// we cannot perform these tests if it's not supported by our environment -if ( common.require( 'util' ).definePropertyFallback() ) +require( 'common' ).testCase( { - return; -} + caseSetUp: function() + { + this.Sut = this.require( 'VisibilityObjectFactory' ); - // SUT -var VisibilityObjectFactory = common.require( 'VisibilityObjectFactory' ), + // properties are expected to be in a specific format + this.props = { + 'public': { + pub: [ [ 'foo' ], {} ], + }, + 'protected': { + prot: [ [ 'bar' ], {} ], + }, + 'private': { + priv: [ [ 'baz' ], {} ], + }, + }; - sut = VisibilityObjectFactory(), + this.methods = { + 'public': { + fpub: ( function() + { + var retval = function() {}; + retval.___$$keywords$$ = { 'public': true }; - // properties are expected to be in a specific format - props = { - 'public': { - pub: [ [ 'foo' ], {} ], - }, - 'protected': { - prot: [ [ 'bar' ], {} ], - }, - 'private': { - priv: [ [ 'baz' ], {} ], - }, + return retval; + } )(), + }, + 'protected': { + fprot: function() {}, + }, + 'private': { + fpriv: function() {}, + }, + }; }, - methods = { - 'public': { - fpub: ( function() - { - var retval = function() {}; - retval.___$$keywords$$ = { 'public': true }; - return retval; - } )(), - }, - 'protected': { - fprot: function() {}, - }, - 'private': { - fpriv: function() {}, - }, - } -; - - -/** - * To keep with the spirit of ease.js, we should be able to instantiate - * VisibilityObjectFactory both with and without the 'new' keyword - * - * Consistency is key with these sorts of things. - */ -( function testCanInstantiateWithAndWithoutNewKeyword() -{ - // with 'new' keyword - assert.ok( - ( new VisibilityObjectFactory() ) instanceof VisibilityObjectFactory, - "Should be able to instantiate VisibilityObjectFactory with 'new' " + - "keyword" - ); - - // without 'new' keyword - assert.ok( VisibilityObjectFactory() instanceof VisibilityObjectFactory, - "Should be able to instantiate VisibilityObjectFactory without 'new' " + - "keyword" - ); -} )(); - - -/** - * One of the core requirements for proper visibility support is the ability to - * create a proxy object. Proxy objects transfer gets/sets of a certain property - * to another object. This allows objects to be layered atop each other while - * still permitting gets/sets to fall through. - */ -( function testCanCreatePropertyProxy() -{ - var base = {}, - dest = {}, - props = { one: true, two: true, three: true }, - val = 'foo', - val2 = 'bar' - ; - - // create proxy of props to base on dest - sut.createPropProxy( base, dest, props ); - - // check to ensure the properties are properly proxied - for ( var prop in props ) + setUp: function() { - dest[ prop ] = val; + // we cannot perform these tests if they are not supported by our + // environment + if ( this.require( 'util' ).definePropertyFallback() ) + { + this.skip(); + } - // check proxy - assert.equal( dest[ prop ], val, - "Property can be set/retrieved on destination object" + this.sut = this.Sut(); + }, + + + /** + * To keep with the spirit of ease.js, we should be able to instantiate + * VisibilityObjectFactory both with and without the 'new' keyword + * + * Consistency is key with these sorts of things. + */ + 'Can instantiate with and without `new` keyword': function() + { + // with `new` keyword + this.assertOk( + ( new this.Sut() ) instanceof this.Sut, + "Should be able to instantiate VisibilityObjectFactory with " + + "'new' keyword" ); - // check base - assert.equal( base[ prop ], val, - "Property can be set via proxy and retrieved on base" + // without `new` keyword + this.assertOk( this.Sut() instanceof this.Sut, + "Should be able to instantiate VisibilityObjectFactory without " + + "'new' keyword" + ); + }, + + + /** + * One of the core requirements for proper visibility support is the ability + * to create a proxy object. Proxy objects transfer gets/sets of a certain + * property to another object. This allows objects to be layered atop each + * other while still permitting gets/sets to fall through. + */ + 'Can create property proxy': function() + { + var _self = this, + base = {}, + dest = {}, + props = { one: true, two: true, three: true }, + val = 'foo', + val2 = 'bar' + ; + + // create proxy of props to base on dest + this.sut.createPropProxy( base, dest, props ); + + // check to ensure the properties are properly proxied + for ( var prop in props ) + { + dest[ prop ] = val; + + // check proxy + _self.assertEqual( dest[ prop ], val, + "Property can be set/retrieved on destination object" + ); + + // check base + _self.assertEqual( base[ prop ], val, + "Property can be set via proxy and retrieved on base" + ); + + // set to new value + base[ prop ] = val2; + + // re-check proxy + _self.assertEqual( dest[ prop ], val2, + "Property can be set on base and retrieved on dest object" + ); + } + }, + + + /** + * An additional layer should be created, which will hold the private + * members. + */ + 'Setup creates private layer': function() + { + var dest = { foo: [] }, + obj = this.sut.setup( dest, this.props, this.methods ); + + this.assertNotEqual( obj, dest, + "Returned object should not be the destination object" ); - // set to new value - base[ prop ] = val2; - - // re-check proxy - assert.equal( dest[ prop ], val2, - "Property can be set on base and retrieved on dest object" + this.assertStrictEqual( obj.foo, dest.foo, + "Destination object is part of the prototype chain of the " + + "returned obj" ); - } -} )(); + }, -/** - * An additional layer should be created, which will hold the private members. - */ -( function testSetupCreatesPrivateLayer() -{ - var dest = { foo: [] }, - obj = sut.setup( dest, props, methods ); + /** + * All protected properties must be proxied from the private layer to the + * protected. Otherwise, sets would occur on the private object, which would + * prevent them from being accessed by subtypes if set by a parent method + * invocation. (The same is true in reverse.) + */ + 'Private layer includes protected member proxy': function() + { + var dest = {}, + obj = this.sut.setup( dest, this.props, this.methods ), + val = 'foo' + ; - assert.notEqual( obj, dest, - "Returned object should not be the destination object" - ); - - assert.strictEqual( obj.foo, dest.foo, - "Destination object is part of the prototype chain of the returned obj" - ); -} )(); + obj.prot = val; + this.assertEqual( dest.prot, val, + "Protected values are proxied from private layer" + ); + }, -/** - * All protected properties must be proxied from the private layer to the - * protected. Otherwise, sets would occur on the private object, which would - * prevent them from being accessed by subtypes if set by a parent method - * invocation. (The same is true in reverse.) - */ -( function testPrivateLayerIncludesProtectedMemberProxy() -{ - var dest = {}, - obj = sut.setup( dest, props, methods ), - val = 'foo' - ; + /** + * Public properties should be initialized on the destination object to + * ensure that references are not shared between instances (that'd be a + * pretty nasty bug). + * + * Note that we do not care about public methods, because they're assumed to + * already be part of the prototype chain. The visibility object is only + * intended to handle levels of visibility that are not directly implemented + * in JS. Public methods are a direct consequence of adding a property to + * the prototype chain. + */ + 'Public properties are copied to destination object': function() + { + var dest = {}; + this.sut.setup( dest, this.props, this.methods ); - obj.prot = val; - assert.equal( dest.prot, val, - "Protected values are proxied from private layer" - ); -} )(); + // values should match + this.assertEqual( dest.pub[ 0 ], this.props[ 'public' ].pub[ 0 ], + "Public properties are properly initialized" + ); + + // ensure references are not shared (should be cloned) + this.assertNotStrictEqual( dest.pub, this.props[ 'public' ].pub, + "Public properties should not be copied by reference" + ); + + // method references should NOT be transferred (they're assumed to + // already be a part of the prototype chain, since they're outside the + // scope of the visibility object) + this.assertEqual( dest.fpub, undefined, + "Public method references should not be copied" + ); + }, -/** - * Public properties should be initialized on the destination object to ensure - * that references are not shared between instances (that'd be a pretty nasty - * bug). - * - * Note that we do not care about public methods, because they're assumed to - * already be part of the prototype chain. The visibility object is only - * intended to handle levels of visibility that are not directly implemented in - * JS. Public methods are a direct consequence of adding a property to the - * prototype chain. - */ -( function testPublicPropertiesAreCopiedToDestinationObject() -{ - var dest = {}; - sut.setup( dest, props, methods ); + /** + * Protected properties should be copied over for the same reason that + * public properties should, in addition to the fact that the protected + * members are not likely to be present on the destination object. In + * addition, methods will be copied over. + */ + 'Protected properties and methods are added to dest object': function() + { + var dest = {}; + this.sut.setup( dest, this.props, this.methods ); - // values should match - assert.equal( dest.pub[ 0 ], props[ 'public' ].pub[ 0 ], - "Public properties are properly initialized" - ); + // values should match + this.assertEqual( dest.prot[ 0 ], this.props[ 'protected' ].prot[ 0 ], + "Protected properties are properly initialized" + ); - // ensure references are not shared (should be cloned) - assert.notStrictEqual( dest.pub, props[ 'public' ].pub, - "Public properties should not be copied by reference" - ); + // ensure references are not shared (should be cloned) + this.assertNotStrictEqual( dest.prot, this.props[ 'protected' ].prot, + "Protected properties should not be copied by reference" + ); - // method references should NOT be transferred (they're assumed to already - // be a part of the prototype chain, since they're outside the scope of the - // visibility object) - assert.equal( dest.fpub, undefined, - "Public method references should not be copied" - ); -} )(); + // protected method references should be copied + this.assertStrictEqual( dest.fprot, this.methods[ 'protected' ].fprot, + "Protected members should be copied by reference" + ); + }, -/** - * Protected properties should be copied over for the same reason that public - * properties should, in addition to the fact that the protected members are not - * likely to be present on the destination object. In addition, methods will be - * copied over. - */ -( function testProtectedPropertiesAndMethodsAreAddedToDestinationObject() -{ - var dest = {}; - sut.setup( dest, props, methods ); + /** + * Public members should *always* take precedence over protected. The reason + * for this is because, if a protected member is overridden and made public + * by a subtype, we need to ensure that the protected member of the + * supertype doesn't take precedence. The reason it would take precedence by + * default is because the protected visibility object is laid *atop* the + * public, meaning it comes first in the prototype chain. + */ + 'Public methods are not overwritten by default': function() + { + // use the public method + var dest = { fpub: this.methods[ 'public' ].fpub }; - // values should match - assert.equal( dest.prot[ 0 ], props[ 'protected' ].prot[ 0 ], - "Protected properties are properly initialized" - ); + // add duplicate method to protected + this.methods[ 'protected' ].fpub = function() {}; - // ensure references are not shared (should be cloned) - assert.notStrictEqual( dest.prot, props[ 'protected' ].prot, - "Protected properties should not be copied by reference" - ); + this.sut.setup( dest, this.props, this.methods ); - // protected method references should be copied - assert.strictEqual( dest.fprot, methods[ 'protected' ].fprot, - "Protected members should be copied by reference" - ); -} )(); + // ensure our public method is still referenced + this.assertStrictEqual( dest.fpub, this.methods[ 'public' ].fpub, + "Public methods should not be overwritten by protected methods" + ); + }, -/** - * Public members should *always* take precedence over protected. The reason for - * this is because, if a protected member is overridden and made public by a - * subtype, we need to ensure that the protected member of the supertype doesn't - * take precedence. The reason it would take precedence by default is because - * the protected visibility object is laid *atop* the public, meaning it comes - * first in the prototype chain. - */ -( function testPublicMethodsAreNotOverwrittenByProtected() -{ - // use the public method - var dest = { fpub: methods[ 'public' ].fpub }; + /** + * Same situation with private members as protected, with the exception that + * we do not need to worry about the overlay problem (in regards to + * methods). This is simply because private members are not inherited. + */ + 'Private properties and methods are added to dest object': function() + { + var dest = {}, + obj = this.sut.setup( dest, this.props, this.methods ); - // add duplicate method to protected - methods[ 'protected' ].fpub = function() {}; + // values should match + this.assertEqual( obj.priv[ 0 ], this.props[ 'private' ].priv[ 0 ], + "Private properties are properly initialized" + ); - sut.setup( dest, props, methods ); - - // ensure our public method is still referenced - assert.strictEqual( dest.fpub, methods[ 'public' ].fpub, - "Public methods should not be overwritten by protected methods" - ); -} )(); - - -/** - * Same situation with private members as protected, with the exception that we - * do not need to worry about the overlay problem (in regards to methods). This - * is simply because private members are not inherited. - */ -( function testPrivatePropertiesAndMethodsAreAddedToDestinationObject() -{ - var dest = {}, - obj = sut.setup( dest, props, methods ); - - // values should match - assert.equal( obj.priv[ 0 ], props[ 'private' ].priv[ 0 ], - "Private properties are properly initialized" - ); - - // ensure references are not shared (should be cloned) - assert.notStrictEqual( obj.priv, props[ 'private' ].priv, - "Private properties should not be copied by reference" - ); - - // private method references should be copied - assert.strictEqual( obj.fpriv, methods[ 'private' ].fpriv, - "Private members should be copied by reference" - ); -} )(); + // ensure references are not shared (should be cloned) + this.assertNotStrictEqual( obj.priv, this.props[ 'private' ].priv, + "Private properties should not be copied by reference" + ); + // private method references should be copied + this.assertStrictEqual( obj.fpriv, this.methods[ 'private' ].fpriv, + "Private members should be copied by reference" + ); + }, +} );