1
0
Fork 0

Converted a number of test cases to new XUnit-style format

perfodd
Mike Gerwitz 2012-05-03 17:21:37 -04:00
parent 0d306b63c8
commit 28bf9e6421
No known key found for this signature in database
GPG Key ID: F22BB8158EE30EAB
5 changed files with 790 additions and 766 deletions

View File

@ -21,91 +21,90 @@
* @author Mike Gerwitz * @author Mike Gerwitz
*/ */
require( 'common' ).testCase(
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()
{ {
// with 'new' keyword caseSetUp: function()
assert.ok( {
( new FallbackVisibilityObjectFactory() ) this.Sut = this.require( 'FallbackVisibilityObjectFactory' );
instanceof FallbackVisibilityObjectFactory,
"Should be able to instantiate FallbackVisibilityObjectFactory with " +
"'new' keyword"
);
// without 'new' keyword // parent of SUT
assert.ok( this.VisibilityObjectFactory =
FallbackVisibilityObjectFactory() this.require( 'VisibilityObjectFactory' );
instanceof FallbackVisibilityObjectFactory,
"Should be able to instantiate FallbackVisibilityObjectFactory " + this.props = this.methods = {
"without 'new' keyword" 'public': {},
); 'protected': {},
} )(); 'private': {},
};
},
/** /**
* VisibilityObjectFactory should be part of our prototype chain. * To keep with the spirit of ease.js, we should be able to instantiate
*/ * VisibilityObjectFactory both with and without the 'new' keyword
( function testInheritsFromVisibilityObjectFactory() *
{ * Consistency is key with these sorts of things.
// check an instance, rather than __proto__, because older engines do not */
// support it 'Can instantiate with and without `new` keyword': function()
assert.ok( {
FallbackVisibilityObjectFactory() instanceof VisibilityObjectFactory, // with 'new' keyword
"Fallback should inherit from VisibilityObjectFactory" 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 * VisibilityObjectFactory should be part of our prototype chain.
* any layers, for that matter). Ensure it's not created. */
*/ 'Inherits from visibility object factory': function()
( function testSetupMethodShouldNotAddPrivateLayer() {
{ // check an instance, rather than __proto__, because older engines do
var dest = {}, // not support it
obj = sut.setup( dest, props, methods ); this.assertOk(
this.Sut() instanceof this.VisibilityObjectFactory,
assert.strictEqual( dest, obj, "Fallback should inherit from VisibilityObjectFactory"
"Private visibility layer is not added atop destination" );
); },
} )();
( function testCreatingPropertyProxyShouldSimplyReturnSelf() /**
{ * We're falling back because we do not support the private visibility layer
var base = {}, * (or any layers, for that matter). Ensure it's not created.
dest = {}; */
'Setup method should not add private layer': function()
{
var dest = {},
obj = this.Sut().setup( dest, this.props, this.methods );
assert.strictEqual( this.assertStrictEqual( dest, obj,
sut.createPropProxy( base, dest, props ), "Private visibility layer is not added atop destination"
base, );
"Creating property proxy should simply return original object" },
);
} )();
/**
* 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"
);
},
} );

View File

@ -22,96 +22,97 @@
*/ */
var common = require( './common' ), require( 'common' ).testCase(
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()
{ {
// with 'new' keyword caseSetUp: function()
assert.ok( {
( new Sut() ) this.Sut = this.require( 'MethodWrapperFactory' );
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"
);
} )();
/** /**
* The factory itself is rather simple. The class should accept a factory * To keep with the spirit of ease.js, we should be able to instantiate
* function which should return the wrapped method. * MethodWrapperFactory both with and without the 'new' keyword
*/ *
( function testProvidedFactoryFunctionIsProperlyCalled() * Consistency is key with these sorts of things.
{ */
var called = false, 'Can instantiate with and without new keyword': function()
method = function() {}, {
super_method = function() {}, // with 'new' keyword
cid = 55, this.assertOk(
getInst = function() {}, ( new this.Sut() ) instanceof this.Sut,
name = 'someMethod', "Should be able to instantiate MethodWrapperFactory with " +
keywords = { 'static': true, 'public': true }, "'new' keyword"
retval = 'foobar'; );
var result = Sut( // without 'new' keyword
function( this.assertOk( ( this.Sut() instanceof this.Sut ),
given_method, given_super, given_cid, givenGetInst, given_name, "Should be able to instantiate MethodWrapperFactory " +
given_keywords "without 'new' keyword"
) );
{ },
called = true;
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, var result = this.Sut(
"Factory method should be provided with cid" function(
); given_method, given_super, given_cid, givenGetInst, given_name,
given_keywords
)
{
called = true;
assert.equal( givenGetInst, getInst, _self.assertEqual( given_method, method,
"Factory method should be provided with proper inst function" "Factory method should be provided with method to wrap"
); );
assert.equal( given_name, name, _self.assertEqual( given_super, super_method,
"Factory method should be provided with proper method name" "Factory method should be provided with super method"
); );
assert.equal( given_keywords, keywords, _self.assertEqual( given_cid, cid,
"Factory method should be provided with proper keywords" "Factory method should be provided with cid"
); );
return retval; _self.assertEqual( givenGetInst, getInst,
} "Factory method should be provided with proper inst function"
).wrapMethod( method, super_method, cid, getInst, name, keywords ); );
// we'll include this in addition to the following assertion (which is _self.assertEqual( given_name, name,
// redundant) to make debugging more clear "Factory method should be provided with proper method name"
assert.equal( called, true, );
"Given factory method should be called"
);
assert.equal( result, retval, _self.assertEqual( given_keywords, keywords,
"Should return value from factory function" "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"
);
},
} );

View File

@ -21,363 +21,375 @@
* @author Mike Gerwitz * @author Mike Gerwitz
*/ */
var common = require( './common' ), require( 'common' ).testCase(
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()
{ {
var instance = function() {}, caseSetUp: function()
val = 'fooboo', {
val2 = 'fooboo2', // common assertions between a couple of proxy tests
iid = 1, this.proxyErrorAssertCommon = function( e, prop, method )
called = false,
getInst = function()
{ {
called = true; this.assertOk(
return instance; e.message.search( 'Unable to proxy' ) > -1,
}, "Unexpected error received: " + e.message
);
method = sut.standard.wrapNew( this.assertOk(
function() ( ( 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( method = this._sut.standard.wrapNew(
function() 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 // super method to be overridden
instance.foo = val; method = this._sut.standard.wrapNew(
instance.foo2 = val2; function()
{
super_called = true;
},
null, 0, getInst
),
assert.equal( method(), val, // the overriding method
"Calling method will bind 'this' to passed instance" override = this._sut.standard.wrapOverride(
); function()
{
this.__super();
},
method, 0, getInst
)
;
assert.equal( override(), val2, // call the overriding method
"Calling method override will bind 'this' to passed instance" 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 * The proxy wrapper should forward all arguments to the provided object's
* overridden method (method of the supertype). * appropriate method. The return value should also be proxied back to the
* * caller.
* 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 'Proxy will properly forward calls to destination object': function()
* 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. var name = 'someMethod',
*/ propname = 'dest',
( function testOverridenMethodShouldContainReferenceToSuperMethod()
{
var orig_called = false,
getInst = function() {},
// "super" method args = [ 1, {}, 'three' ],
method = sut.standard.wrapNew( args_given = [],
function()
{
orig_called = true;
},
null, 0, getInst
),
// override method getInst = function()
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()
{ {
return inst; return inst;
}, },
method
).call( inst )
;
assert.strictEqual( inst, ret, method_retval = {},
"Proxy should return instance in place of destination, if returned" 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 ) * 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
assert.ok( * encapsulation. Consequently, it also provides a more consistent and
e.message.search( 'Unable to proxy' ) > -1, * sensical API and permits method chaining.
"Unexpected error received: " + e.message *
); * 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
assert.ok( * call.
( ( e.message.search( prop ) > -1 ) */
&& ( e.message.search( method ) > -1 ) 'Proxy retval is replaced with context if dest returns self': function()
),
"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
{ {
// should fail because 'noexist' does not exist on the object var propname = 'foo',
sut.standard.wrapProxy( method = 'bar',
prop, null, 0,
function() { return {}; }, foo = {
method bar: function()
)(); {
} // return "self"
catch ( e ) 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 ); var prop = 'noexist',
return; method = 'foo';
}
assert.fail( try
"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()
{ {
// pretend that we're a static class with a static accessor method // should fail because 'noexist' does not exist on the object
return { this._sut.standard.wrapProxy(
$: function( name ) prop, null, 0,
{ function() { return {}; },
// implicitly tests that the argument is properly passed method
// (would otherwise return `undefined`) )();
return s[ name ]; }
}, 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 }; this.assertStrictEqual( val,
this._sut.standard.wrapProxy(
val = [ 'value' ], 'foo', null, 0, getInst, 'method', keywords
s = { )(),
// destination object "Should properly proxy to static membesr via static accessor method"
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"
);
} )();

View File

@ -21,57 +21,62 @@
* @author Mike Gerwitz * @author Mike Gerwitz
*/ */
require( 'common' ).testCase(
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()
{ {
// don't bother with the test if we don't support the standard visibility caseSetUp: function()
// object
if ( util.definePropertyFallback() )
{ {
return; this.sut = this.require( 'VisibilityObjectFactoryFactory' );
}
assert.ok( this.VisibilityObjectFactory =
( sut.fromEnvironment() instanceof VisibilityObjectFactory ), this.require( 'VisibilityObjectFactory' );
"Creates standard VisibilityObjectFactory if supported"
); 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 * By default, if supported by our environment, we should use the standard
* working implementation that sacrifices visibility support. * factory to provide proper visibility support.
*/ */
( function testReturnsFallbackFactoryIfFallingBack() 'Returns standard factory if not falling back': function()
{ {
var old = util.definePropertyFallback(); // don't bother with the test if we don't support the standard visibility
// object
if ( this.util.definePropertyFallback() )
{
return;
}
// force fallback this.assertOk(
util.definePropertyFallback( true ); ( 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 );
},
} );

View File

@ -21,270 +21,277 @@
* @author Mike Gerwitz * @author Mike Gerwitz
*/ */
require( 'common' ).testCase(
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() )
{ {
return; caseSetUp: function()
} {
this.Sut = this.require( 'VisibilityObjectFactory' );
// SUT // properties are expected to be in a specific format
var VisibilityObjectFactory = common.require( 'VisibilityObjectFactory' ), 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 return retval;
props = { } )(),
'public': { },
pub: [ [ 'foo' ], {} ], 'protected': {
}, fprot: function() {},
'protected': { },
prot: [ [ 'bar' ], {} ], 'private': {
}, fpriv: function() {},
'private': { },
priv: [ [ 'baz' ], {} ], };
},
}, },
methods = {
'public': {
fpub: ( function()
{
var retval = function() {};
retval.___$$keywords$$ = { 'public': true };
return retval; setUp: function()
} )(),
},
'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 )
{ {
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 this.sut = this.Sut();
assert.equal( dest[ prop ], val, },
"Property can be set/retrieved on destination object"
/**
* 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 // without `new` keyword
assert.equal( base[ prop ], val, this.assertOk( this.Sut() instanceof this.Sut,
"Property can be set via proxy and retrieved on base" "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 this.assertStrictEqual( obj.foo, dest.foo,
base[ prop ] = val2; "Destination object is part of the prototype chain of the " +
"returned obj"
// re-check proxy
assert.equal( 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. * All protected properties must be proxied from the private layer to the
*/ * protected. Otherwise, sets would occur on the private object, which would
( function testSetupCreatesPrivateLayer() * prevent them from being accessed by subtypes if set by a parent method
{ * invocation. (The same is true in reverse.)
var dest = { foo: [] }, */
obj = sut.setup( dest, props, methods ); 'Private layer includes protected member proxy': function()
{
var dest = {},
obj = this.sut.setup( dest, this.props, this.methods ),
val = 'foo'
;
assert.notEqual( obj, dest, obj.prot = val;
"Returned object should not be the destination object" this.assertEqual( dest.prot, val,
); "Protected values are proxied from private layer"
);
assert.strictEqual( obj.foo, dest.foo, },
"Destination object is part of the prototype chain of the returned obj"
);
} )();
/** /**
* All protected properties must be proxied from the private layer to the * Public properties should be initialized on the destination object to
* protected. Otherwise, sets would occur on the private object, which would * ensure that references are not shared between instances (that'd be a
* prevent them from being accessed by subtypes if set by a parent method * pretty nasty bug).
* invocation. (The same is true in reverse.) *
*/ * Note that we do not care about public methods, because they're assumed to
( function testPrivateLayerIncludesProtectedMemberProxy() * already be part of the prototype chain. The visibility object is only
{ * intended to handle levels of visibility that are not directly implemented
var dest = {}, * in JS. Public methods are a direct consequence of adding a property to
obj = sut.setup( dest, props, methods ), * the prototype chain.
val = 'foo' */
; 'Public properties are copied to destination object': function()
{
var dest = {};
this.sut.setup( dest, this.props, this.methods );
obj.prot = val; // values should match
assert.equal( dest.prot, val, this.assertEqual( dest.pub[ 0 ], this.props[ 'public' ].pub[ 0 ],
"Protected values are proxied from private layer" "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 * Protected properties should be copied over for the same reason that
* that references are not shared between instances (that'd be a pretty nasty * public properties should, in addition to the fact that the protected
* bug). * members are not likely to be present on the destination object. In
* * addition, methods will be copied over.
* 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 'Protected properties and methods are added to dest object': function()
* 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 var dest = {};
* prototype chain. this.sut.setup( dest, this.props, this.methods );
*/
( function testPublicPropertiesAreCopiedToDestinationObject()
{
var dest = {};
sut.setup( dest, props, methods );
// values should match // values should match
assert.equal( dest.pub[ 0 ], props[ 'public' ].pub[ 0 ], this.assertEqual( dest.prot[ 0 ], this.props[ 'protected' ].prot[ 0 ],
"Public properties are properly initialized" "Protected properties are properly initialized"
); );
// ensure references are not shared (should be cloned) // ensure references are not shared (should be cloned)
assert.notStrictEqual( dest.pub, props[ 'public' ].pub, this.assertNotStrictEqual( dest.prot, this.props[ 'protected' ].prot,
"Public properties should not be copied by reference" "Protected properties should not be copied by reference"
); );
// method references should NOT be transferred (they're assumed to already // protected method references should be copied
// be a part of the prototype chain, since they're outside the scope of the this.assertStrictEqual( dest.fprot, this.methods[ 'protected' ].fprot,
// visibility object) "Protected members should be copied by reference"
assert.equal( dest.fpub, undefined, );
"Public method references should not be copied" },
);
} )();
/** /**
* Protected properties should be copied over for the same reason that public * Public members should *always* take precedence over protected. The reason
* properties should, in addition to the fact that the protected members are not * for this is because, if a protected member is overridden and made public
* likely to be present on the destination object. In addition, methods will be * by a subtype, we need to ensure that the protected member of the
* copied over. * supertype doesn't take precedence. The reason it would take precedence by
*/ * default is because the protected visibility object is laid *atop* the
( function testProtectedPropertiesAndMethodsAreAddedToDestinationObject() * public, meaning it comes first in the prototype chain.
{ */
var dest = {}; 'Public methods are not overwritten by default': function()
sut.setup( dest, props, methods ); {
// use the public method
var dest = { fpub: this.methods[ 'public' ].fpub };
// values should match // add duplicate method to protected
assert.equal( dest.prot[ 0 ], props[ 'protected' ].prot[ 0 ], this.methods[ 'protected' ].fpub = function() {};
"Protected properties are properly initialized"
);
// ensure references are not shared (should be cloned) this.sut.setup( dest, this.props, this.methods );
assert.notStrictEqual( dest.prot, props[ 'protected' ].prot,
"Protected properties should not be copied by reference"
);
// protected method references should be copied // ensure our public method is still referenced
assert.strictEqual( dest.fprot, methods[ 'protected' ].fprot, this.assertStrictEqual( dest.fpub, this.methods[ 'public' ].fpub,
"Protected members should be copied by reference" "Public methods should not be overwritten by protected methods"
); );
} )(); },
/** /**
* Public members should *always* take precedence over protected. The reason for * Same situation with private members as protected, with the exception that
* this is because, if a protected member is overridden and made public by a * we do not need to worry about the overlay problem (in regards to
* subtype, we need to ensure that the protected member of the supertype doesn't * methods). This is simply because private members are not inherited.
* take precedence. The reason it would take precedence by default is because */
* the protected visibility object is laid *atop* the public, meaning it comes 'Private properties and methods are added to dest object': function()
* first in the prototype chain. {
*/ var dest = {},
( function testPublicMethodsAreNotOverwrittenByProtected() obj = this.sut.setup( dest, this.props, this.methods );
{
// use the public method
var dest = { fpub: methods[ 'public' ].fpub };
// add duplicate method to protected // values should match
methods[ 'protected' ].fpub = function() {}; this.assertEqual( obj.priv[ 0 ], this.props[ 'private' ].priv[ 0 ],
"Private properties are properly initialized"
);
sut.setup( dest, props, methods ); // ensure references are not shared (should be cloned)
this.assertNotStrictEqual( obj.priv, this.props[ 'private' ].priv,
// ensure our public method is still referenced "Private properties should not be copied by reference"
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"
);
} )();
// private method references should be copied
this.assertStrictEqual( obj.fpriv, this.methods[ 'private' ].fpriv,
"Private members should be copied by reference"
);
},
} );