1
0
Fork 0
easejs/test/VisibilityObjectFactoryTest.js

291 lines
8.6 KiB
JavaScript

/**
* Tests visibility object factory
*
* Copyright (C) 2010 Mike Gerwitz
*
* This file is part of ease.js.
*
* ease.js is free software: you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @author Mike Gerwitz
*/
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;
}
// SUT
var VisibilityObjectFactory = common.require( 'VisibilityObjectFactory' ),
sut = VisibilityObjectFactory(),
// properties are expected to be in a specific format
props = {
'public': {
pub: [ [ 'foo' ], {} ],
},
'protected': {
prot: [ [ 'bar' ], {} ],
},
'private': {
priv: [ [ 'baz' ], {} ],
},
},
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 ( prop in props )
{
dest[ prop ] = val;
// check proxy
assert.equal( dest[ prop ], val,
"Property can be set/retrieved on destination object"
);
// check base
assert.equal( base[ prop ], val,
"Property can be set via proxy and retrieved on base"
);
// 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"
);
}
} )();
/**
* An additional layer should be created, which will hold the private members.
*/
( function testSetupCreatesPrivateLayer()
{
var dest = { foo: [] },
obj = sut.setup( dest, props, methods );
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"
);
} )();
/**
* 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'
;
obj.prot = val;
assert.equal( dest.prot, val,
"Protected values are proxied from private layer"
);
} )();
/**
* 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 );
// values should match
assert.equal( dest.pub[ 0 ], props[ 'public' ].pub[ 0 ],
"Public 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"
);
// 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 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 );
// values should match
assert.equal( dest.prot[ 0 ], props[ 'protected' ].prot[ 0 ],
"Protected properties are properly initialized"
);
// ensure references are not shared (should be cloned)
assert.notStrictEqual( dest.prot, props[ 'protected' ].prot,
"Protected properties should not be copied by reference"
);
// protected method references should be copied
assert.strictEqual( dest.fprot, methods[ 'protected' ].fprot,
"Protected members should be copied by reference"
);
} )();
/**
* 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 };
// add duplicate method to protected
methods[ 'protected' ].fpub = function() {};
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"
);
} )();