Converted a number of test cases to new XUnit-style format
parent
0d306b63c8
commit
28bf9e6421
|
@ -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"
|
||||
);
|
||||
},
|
||||
} );
|
||||
|
||||
|
|
|
@ -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"
|
||||
);
|
||||
},
|
||||
} );
|
||||
|
|
|
@ -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"
|
||||
);
|
||||
},
|
||||
} );
|
||||
|
||||
|
|
|
@ -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 );
|
||||
},
|
||||
} );
|
||||
|
|
|
@ -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"
|
||||
);
|
||||
},
|
||||
} );
|
||||
|
|
Loading…
Reference in New Issue