1
0
Fork 0
easejs/test/test-class_builder-static.js

1121 lines
34 KiB
JavaScript
Raw Normal View History

/**
* Tests static members
*
2011-12-23 00:09:01 -05:00
* Copyright (C) 2010,2011 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' ),
fallback = common.require( 'util' ).definePropertyFallback(),
// XXX: get rid of this disgusting mess; we're mid-refactor and all these
// dependencies should not be necessary for testing
ClassBuilder = common.require( '/ClassBuilder' ),
MethodWrapperFactory = common.require( '/MethodWrapperFactory' ),
wrappers = common.require( '/MethodWrappers' ).standard
;
2011-04-05 22:07:13 -04:00
require( 'common' ).testCase(
2011-04-05 22:07:13 -04:00
{
setUp: function()
{
this.builder = ClassBuilder(
common.require( '/MemberBuilder' )(
MethodWrapperFactory( wrappers.wrapNew ),
MethodWrapperFactory( wrappers.wrapOverride ),
Added `proxy' keyword support The concept of proxy methods will become an important, core concept in ease.js that will provide strong benefits for creating decorators and proxies, removing boilerplate code and providing useful metadata to the system. Consider the following example: Class( 'Foo', { // ... 'public performOperation': function( bar ) { this._doSomethingWith( bar ); return this; }, } ); Class( 'FooDecorator', { 'private _foo': null, // ... 'public performOperation': function( bar ) { return this._foo.performOperation( bar ); }, } ); In the above example, `FooDecorator` is a decorator for `Foo`. Assume that the `getValueOf()` method is undecorated and simply needs to be proxied to its component --- an instance of `Foo`. (It is not uncommon that a decorator, proxy, or related class will alter certain functionality while leaving much of it unchanged.) In order to do so, we can use this generic, boilerplate code return this.obj.func.apply( this.obj, arguments ); which would need to be repeated again and again for *each method that needs to be proxied*. We also have another problem --- `Foo.getValueOf()` returns *itself*, which `FooDecorator` *also* returns. This breaks encapsulation, so we instead need to return ourself: 'public performOperation': function( bar ) { this._foo.performOperation( bar ); return this; }, Our boilerplate code then becomes: var ret = this.obj.func.apply( this.obj, arguments ); return ( ret === this.obj ) ? this : ret; Alternatively, we could use the `proxy' keyword: Class( 'FooDecorator2', { 'private _foo': null, // ... 'public proxy performOperation': '_foo', } ); `FooDecorator2.getValueOf()` and `FooDecorator.getValueOf()` both perform the exact same task --- proxy the entire call to another object and return its result, unless the result is the component, in which case the decorator itself is returned. Proxies, as of this commit, accomplish the following: - All arguments are forwarded to the destination - The return value is forwarded to the caller - If the destination returns a reference to itself, it will be replaced with a reference to the caller's context (`this`). - If the call is expected to fail, either because the destination is not an object or because the requested method is not a function, a useful error will be immediately thrown (rather than the potentially cryptic one that would otherwise result, requiring analysis of the stack trace). N.B. As of this commit, static proxies do not yet function properly.
2012-05-02 13:26:47 -04:00
MethodWrapperFactory( wrappers.wrapProxy ),
this.getMock( 'MemberBuilderValidator' )
),
common.require( '/VisibilityObjectFactoryFactory' ).fromEnvironment()
);
},
/**
* To provide access to static members, this.__self is made available inside
* of instances.
*/
'Self property references class definition': function()
{
var val = [ 'baz' ],
Foo = this.builder.build(
{
'public test': function()
{
return this.__self;
},
} );
Foo.bar = val;
// we must use instanceof here because the __self object has the class
// in its prototype chain
this.assertOk( ( Foo().test().bar === Foo.bar ),
"__self property references class definition"
);
},
2011-04-05 22:07:13 -04:00
/**
* If a static property does not exist, the getter should return undefined.
*
* This test exists to ensure an error is not thrown if the property is not
* found. This is because we check each parent and eventually reach the base
* object. We must ensure the base object does not cause any problems.
*/
'Static property lookup returns undefined if not found': function()
{
var result = this.builder.build( {} ).$( 'foo' );
this.assertEqual( result, undefined,
"Static property getter should return undefined if not found"
);
},
/**
* If supported by the environment, ensure that the accessor method used to
* access static properties is not enumerable. It's unnecessary clutter (and
* confusion) otherwise.
*/
'Static property accessor is not enumerable': function()
{
var get = Object.getOwnPropertyDescriptor,
Foo = this.builder.build( {} );
// don't perform the test if unsupported
if ( fallback )
{
return;
}
this.assertEqual( get( Foo, '$' ).enumerable, false,
"Static property accessor method should not be enumerable"
);
},
/**
* Static members, by their nature, should be accessible through the class
* definition itself; that is, without instantiation. It should also not be
* available through the generated prototype (and therefore, be unavailable
* to instances).
*/
'Public static members are accessible via class definition only': function()
{
var val = 'foo',
val2 = 'bar',
Foo = this.builder.build(
{
'public static foo': val,
// should be public by default
'static bar': val2,
// the same rules should apply to methods
'public static baz': function()
{
return val;
},
'static foobar': function()
{
return val2;
},
} );
// properties should be accessible via class definition
this.assertEqual( Foo.$('foo'), val,
"Public static properties should be accessible via class definition"
);
// as long as the above test succeeded, we can then conclude that static
// members are public by default if the following succeeds
this.assertEqual( Foo.$('bar'), val2,
"Static properties are public by default"
);
// methods should be accessible via class definition
this.assertEqual( Foo.baz(), val,
"Public static methods should be accessible via class definition"
);
// same rules as above, but with a method
this.assertEqual( Foo.foobar(), val2,
"Static methods are public by default"
);
// getter/setter method should not be a part of the prototype
this.assertEqual( Foo.prototype.$, undefined,
"Public static properties are *not* part of the prototype"
);
},
/**
* Same as above, but with getters/setters. We can only run this test if
* getters/setters are supported by the engine running it.
*/
'Public static getters/setter accessible via class dfn only': function()
{
// if unsupported, don't bother with the test
if ( fallback )
{
return;
}
// we must define in this manner so older engines won't blow up due to
// syntax errors
var def = {},
val = 'baz',
called = [];
Object.defineProperty( def, 'public static foo', {
get: function() { return val; },
set: function() { called[ 0 ] = true; },
enumerable: true,
} );
// should be public by default if not specified
Object.defineProperty( def, 'static bar', {
get: function() { return val; },
set: function() { called[ 1 ] = true; },
enumerable: true,
} );
// define the class
var Foo = this.builder.build( def );
this.assertEqual( Foo.foo, val,
"Public static getters are accessible via class definition"
);
Foo.foo = 'moo';
this.assertEqual( called[ 0 ], true,
"Public static setters are accessible via class definition"
);
this.assertEqual( Foo.bar, val,
"Static getters are public by default"
);
Foo.bar = 'moo';
this.assertEqual( called[ 1 ], true,
"Static setters are public by default"
);
// none of these should be available on the prototype
this.assertEqual( Foo.prototype.foo, undefined,
"Public static getters/getters are unavailable on prototype (0)"
);
this.assertEqual( Foo.prototype.bar, undefined,
"Public static getters/getters are unavailable on prototype (1)"
);
},
/**
* With non-static methods, 'this' is bound to the instance. In the case of
* static members, we should bind to the class definition (equivalent of
* this.__self).
*
* This functionality had already existed previously. When a propobj is not
* available for an instance, it falls back. This serves as a regression
* test to ensure this functionality remains.
*/
'Static methods not bound to instance': function()
{
var result = null,
Foo = this.builder.build(
{
'public static foo': function()
{
result = this;
},
} );
// call the static method
Foo.foo();
// note that the objects themselves aren't the same, due to the property
// object
this.assertEqual( result.foo, Foo.foo,
"Static members are bound to class definition rather than instance"
);
},
/**
* We don't have the benefit of static members being part of the prototype
* chain. Inheritance is not automatic. This test deals only with ensuring
* that *public* static members are inherited by subtypes.
*/
'Public static members are inherited by subtypes': function()
{
var def = {
'public static foo': 'val',
'public static func': function() {},
'public bla': 'moo',
};
// also test getters/setters if supported
if ( !fallback )
{
Object.defineProperty( def, 'public static bar', {
get: function() {},
set: function() {},
enumerable: true,
} );
}
var baz = 'foobar',
Foo = this.builder.build( def ),
// extends from the parent and adds an additional
SubFoo = this.builder.build( Foo, { 'public static baz': baz } ),
// simply extends from the parent (also serves as a check to ensure
// that static members of *all* parents are inherited, not just the
// immediate)
SubSubFoo = this.builder.build( SubFoo, {} )
;
// properties
this.assertEqual( SubFoo.$('foo'), Foo.$('foo'),
"Public static properties are inherited by subtypes"
);
this.assertEqual( SubSubFoo.$('foo'), Foo.$('foo'),
"Public static properties are inherited by sub-subtypes"
);
// methods
this.assertDeepEqual( SubFoo.func, Foo.func,
"Public static methods are inherited by subtypes"
);
this.assertDeepEqual( SubSubFoo.func, Foo.func,
"Public static methods are inherited by sub-subtypes"
);
// merge
this.assertEqual( SubFoo.$('baz'), baz,
"Subtypes contain both inherited static members as well as their " +
"own"
);
// getters/setters (if supported by engine)
if ( !fallback )
{
var super_data = Object.getOwnPropertyDescriptor( Foo, 'bar' ),
sub_data = Object.getOwnPropertyDescriptor( SubFoo, 'bar' ),
sub_sub_data = Object.getOwnPropertyDescriptor(
SubSubFoo, 'bar'
)
;
// getters
this.assertDeepEqual( super_data.get, sub_data.get,
"Public static getters are inherited by subtypes"
);
this.assertDeepEqual( super_data.get, sub_sub_data.get,
"Public static getters are inherited by sub-subtypes"
);
// setters
this.assertDeepEqual( super_data.set, sub_data.set,
"Public static setters are inherited by subtypes"
);
this.assertDeepEqual( super_data.set, sub_sub_data.set,
"Public static setters are inherited by sub-subtypes"
);
}
},
/**
* Static references should be inherited by subtypes. That is, modifying a
* static property of a supertype should modify the same static property of
* the subtype, so long as the subtype has not defined a property of the
* same name.
*/
'Public static property references are inherited by subtypes': function()
{
var val = [ 1, 2, 3 ],
val2 = [ 'a', 'b', 'c' ],
Foo = this.builder.build(
{
'public static bar': val,
} ),
SubFoo = this.builder.build( Foo, {} )
;
// the properties should reference the same object
this.assertOk( SubFoo.$('bar') === Foo.$('bar'),
"Inherited static properties should share references"
);
// setting a property on Foo should set the property on SubFoo and
// vice-versa
Foo.$( 'bar', val2 );
this.assertDeepEqual( Foo.$( 'bar' ), val2,
"Can set static property values"
);
this.assertOk( Foo.$( 'bar' ) === SubFoo.$( 'bar' ),
"Setting a static property value on a supertype also sets the " +
"value on subtypes"
);
SubFoo.$( 'bar', val );
this.assertOk( Foo.$( 'bar' ) === SubFoo.$( 'bar' ) );
},
/**
* Static members do not have the benefit of prototype chains. We must
* implement our own means of traversing the inheritance tree. This is done
* by checking to see if a class has defined the requested property, then
* forwarding the call to the parent if it has not.
*
* The process of looking up the property is very important. hasOwnProperty
* is used rather than checking for undefined, because they have drastically
* different results. Setting a value to undefined (if hasOwnProperty were
* not used) would effectively forward all requests to the base class (since
* no property would be found), thereby preventing it from ever being
* written to again.
*/
'Setting static props to undefined will not corrupt lookup': function()
{
var val = 'baz',
Foo = this.builder.build(
{
'public static foo': '',
} )
;
// first check to ensure we can set the value to null
Foo.$( 'foo', null );
this.assertStrictEqual( Foo.$( 'foo' ), null,
"Static properties may be set to null"
);
// then undefined (this actually won't do anything)
Foo.$( 'foo', undefined );
this.assertStrictEqual( Foo.$( 'foo' ), undefined,
"Static properties may be set to undefined"
);
// then set back to a scalar
Foo.$( 'foo', val );
this.assertEqual( Foo.$( 'foo' ), val,
"Setting static property to undefined does not corrupt lookup " +
"process"
);
},
/**
* Ensure that the proper context is returned by static property setters. It
* should return the calling class, regardless of whether or not it owns the
* property being requested.
*/
'Static property setters return proper context': function()
{
var Foo = this.builder.build(
{
'public static foo': '',
} ),
SubFoo = this.builder.build( Foo, {} )
;
this.assertOk( Foo.$( 'foo', 'val' ) === Foo,
"Static property setter returns self"
);
this.assertOk( SubFoo.$( 'foo', 'val' ) === SubFoo,
"Static property setter returns calling class, even if property " +
"is owned by a supertype"
);
},
/**
* Users should not be permitted to set values of static properties that
* have not been declared.
*/
'Attempting to set undeclared static prop results in exception': function()
{
var _self = this;
this.assertThrows(
function()
{
// should throw an exception since property 'foo' has not been
// declared
_self.builder.build( {} ).$( 'foo', 'val' );
},
ReferenceError,
"Attempting to set an undeclaraed static property results in an " +
"exception"
);
},
/**
* Same as above test. In this case, we have to keep in mind that non-class
* bases may not have the static accessor method defined. In that case,
* attempting to call it would cause an error.
*
* Of course, even if they did have a static accessor method defined, we
* wouldn't want to use it, as it isn't the one provided by us. Let's close
* up as many holes in the framework as we can. It's more to prevent
* unintended side-effects than anything. There's not much one could do with
* a "fake" static accessor method that they couldn't with a base class. So,
* for good measure, we'll declare one on our test base.
*/
'Accessing static accessor method on non-class base also works': function()
{
var _self = this,
base = function() {},
Test = _self.builder.build( base, {} )
;
// we do not want to call this
base.$ = function()
{
_self.fail(
"Should not call static accessor method of non-class base"
);
};
// get
this.assertEqual( undefined, Test.$( 'foo' ) );
// set
this.assertThrows(
function()
{
Test.$( 'foo', 'val' );
},
ReferenceError,
"Attempting to set an undeclaraed static property results in an " +
"exception on non-class base"
);
},
/**
* Protected members should be available from within the class but shouldn't
* be exposed to the world
*/
'Protected static members are available inside class only': function()
{
var val = 'foo',
Foo = this.builder.build(
{
'protected static prop': val,
// the same rules should apply to methods
'protected static baz': function()
{
return val;
},
// ensure method is accessible to static methods
'public static staticBaz': function()
{
return this.baz();
},
// ensure method is accessible to instance methods
'public instBaz': function()
{
return this.__self.baz();
},
'public static staticGetProp': function()
{
return this.$('prop');
},
'public instGetProp': function()
{
return this.__self.$('prop');
},
} );
this.assertEqual( Foo.baz, undefined,
"Protected methods should not be accessible outside the class"
);
this.assertEqual( Foo.staticBaz(), val,
"Protected methods are accessible to static methods"
);
this.assertEqual( Foo().instBaz(), val,
"Protected methods are accessible to instance methods"
);
this.assertEqual( Foo.staticGetProp(), val,
"Protected static properties are accessible to static methods"
);
this.assertEqual( Foo().instGetProp(), val,
"Protected static properties are accessible to instance methods"
);
},
/**
* Same as above, but with getters/setters. We can only run this test if
* getters/setters are supported by the engine running it.
*/
'Protected static getters/setters accessible inside class only': function()
{
// if unsupported, don't bother with the test
if ( fallback )
{
return;
}
// we must define in this manner so older engines won't blow up due to
// syntax errors
var def = {
'public static getProp': function()
{
// getters/setters are not accessed using the accessor
// method
return this.foo;
},
'public static setProp': function( val )
{
this.foo = val;
},
},
val = 'baz',
called = [];
Object.defineProperty( def, 'protected static foo', {
get: function() { return val; },
set: function() { called[ 0 ] = true; },
enumerable: true,
} );
// define the class
var Foo = this.builder.build( def );
this.assertEqual( Foo.getProp(), val,
"Protected static getters are accessible from within the class"
);
Foo.setProp( 'bla' );
this.assertEqual( called[ 0 ], true,
"Protected static setters are accessible from within the class"
);
this.assertEqual( Foo.foo, undefined,
"Protected static getters/getters are not public"
);
},
/**
* As usual, protected members (in this case, static) should be inherited by
* subtypes.
*
* Long function is long. Kids, don't do this at home.
*/
'Protected static members are inherited by subtypes': function()
{
var val = 'baz',
val2 = 'bazbaz',
def = {
'protected static prop': val,
'protected static foo': function()
{
return val;
},
};
// also test getters/setters if supported
if ( !fallback )
{
Object.defineProperty( def, 'protected static bar', {
get: function() {},
set: function() {},
enumerable: true,
} );
// used to get the property descriptor of a protected property
def[ 'public static getPropDesc' ] = function( prop )
{
return Object.getOwnPropertyDescriptor( this, prop );
};
}
var Foo = this.builder.build( def ),
SubFoo = this.builder.build( Foo,
{
'public static bar': function()
{
return this.foo();
},
'protected static foo2': function()
{
return val2;
},
'public static bar2': function()
{
return this.foo2();
},
'public static getProp': function()
{
return this.$('prop');
},
} ),
SubSubFoo = this.builder.build( SubFoo, {} )
;
this.assertEqual( SubFoo.bar(), val,
"Subtypes inherit parents' protected static methods"
);
this.assertEqual( SubFoo.bar2(), val2,
"Static methods have access to other static methods in the same " +
"class"
);
// for extra assurance, to ensure our recursive implementation is
// correct
this.assertEqual( SubSubFoo.bar(), val,
"Sub-subtypes inherit parents' protected static methods"
);
this.assertEqual( SubFoo.getProp(), val,
"Subtypes inherit parents' protected static properties"
);
this.assertEqual( SubSubFoo.getProp(), val,
"Sub-subtypes inherit parents' protected static properties"
);
// getters/setters (if supported by engine)
if ( !fallback )
{
var super_data = Foo.getPropDesc( 'bar' ),
sub_data = SubFoo.getPropDesc( 'bar' ),
sub_sub_data = SubSubFoo.getPropDesc( 'bar' )
;
// getters
this.assertDeepEqual( super_data.get, sub_data.get,
"Protected static getters are inherited by subtypes"
);
this.assertDeepEqual( super_data.get, sub_sub_data.get,
"Protected static getters are inherited by sub-subtypes"
);
// setters
this.assertDeepEqual( super_data.set, sub_data.set,
"Protected static setters are inherited by subtypes"
);
this.assertDeepEqual( super_data.set, sub_sub_data.set,
"Protected static setters are inherited by sub-subtypes"
);
}
},
/**
* Private members should be available from within the class, but not
* outside of it
*/
'Private static members are available inside class only': function()
{
var val = 'foo',
Foo = this.builder.build(
{
'private static prop': val,
// the same rules should apply to methods
'private static baz': function()
{
return val;
},
// ensure method is accessible to static methods
'public static staticBaz': function()
{
return this.baz();
},
// ensure method is accessible to instance methods
'public instBaz': function()
{
return this.__self.baz();
},
'public static staticGetProp': function()
{
return this.$('prop');
},
'public instGetProp': function()
{
return this.__self.$('prop');
},
} );
this.assertEqual( Foo.baz, undefined,
"Private methods should not be accessible outside the class"
);
this.assertEqual( Foo.staticBaz(), val,
"Private methods are accessible to static methods"
);
this.assertEqual( Foo().instBaz(), val,
"Private methods are accessible to instance methods"
);
this.assertEqual( Foo.staticGetProp(), val,
"Private static properties are accessible to static methods"
);
this.assertEqual( Foo().instGetProp(), val,
"Private static properties are accessible to instance methods"
);
},
2011-05-13 00:55:09 -04:00
/**
* Private static members should not be inherited by subtypes. Of course.
* Moving along...
*/
'Private static members are not inherited by subtypes': function()
{
var def = {
'private static prop': 'foo',
'private static priv': function() {},
};
2011-05-11 20:10:10 -04:00
if ( !fallback )
{
Object.defineProperty( def, 'private static foo', {
get: function() { return 'foo'; },
set: function() {},
2011-05-11 20:10:10 -04:00
enumerable: true,
} );
}
2011-05-13 00:55:09 -04:00
var Foo = this.builder.build( def ),
2011-05-13 00:55:09 -04:00
SubFoo = this.builder.build( Foo,
2011-05-13 00:55:09 -04:00
{
'public static getPriv': function()
{
return this.priv;
},
'public static getGetSet': function()
{
return this.foo;
},
'public static staticGetProp': function()
{
return this.$('prop');
},
'public instGetProp': function()
{
return this.__self.$('prop');
},
} )
;
2011-05-11 20:10:10 -04:00
this.assertEqual( SubFoo.getPriv(), undefined,
"Private static methods should not be inherited by subtypes"
);
2011-05-13 00:55:09 -04:00
this.assertEqual( SubFoo.getGetSet(), undefined,
"Private static getters/setters should not be inherited by subtypes"
);
2011-05-13 00:55:09 -04:00
this.assertEqual( SubFoo().instGetProp(), undefined,
"Private static properties should not be inherited by subtypes " +
"(inst)"
);
2011-05-11 20:10:10 -04:00
this.assertEqual( SubFoo.staticGetProp(), undefined,
"Private static properties should not be inherited by subtypes " +
"(static)"
);
},
2011-05-11 20:10:10 -04:00
/**
* Same as above, but with getters/setters. We can only run this test if
* getters/setters are supported by the engine running it.
*/
'Private static getters/setters accessible inside class only': function()
{
// if unsupported, don't bother with the test
if ( fallback )
2011-05-11 20:10:10 -04:00
{
return;
}
// we must define in this manner so older engines won't blow up due to
// syntax errors
var def = {
'public static getProp': function()
{
// getters/setters are not accessed using the accessor
// method
return this.foo;
},
'public static setProp': function( val )
{
this.foo = val;
},
2011-05-11 20:10:10 -04:00
},
val = 'baz',
called = [];
2011-05-13 00:55:09 -04:00
Object.defineProperty( def, 'private static foo', {
get: function() { return val; },
set: function() { called[ 0 ] = true; },
enumerable: true,
} );
2011-05-11 20:10:10 -04:00
// define the class
var Foo = this.builder.build( def );
2011-05-13 00:55:09 -04:00
this.assertEqual( Foo.getProp(), val,
"Private static getters are accessible from within the class"
);
Foo.setProp( 'bla' );
this.assertEqual( called[ 0 ], true,
"Private static setters are accessible from within the class"
);
2011-05-13 00:55:09 -04:00
this.assertEqual( Foo.foo, undefined,
"Private static getters/getters are not public"
);
},
2011-05-11 20:10:10 -04:00
/**
* Public and protected static methods should be able to be overridden by
* subtypes. We needn't test private methods, as they are not inherited.
*/
'Static methods can be overridden by subtypes': function()
{
var val = 'bar',
Foo = this.builder.build(
{
'public static foo': function() {},
'protected static bar': function() {},
} ),
SubFoo = this.builder.build( Foo,
{
'public static foo': function()
{
return val;
},
'public static prot': function()
{
return this.bar();
},
'protected static bar': function()
{
return val;
},
} );
this.assertEqual( SubFoo.foo(), val,
"Public static methods can be overridden by subtypes"
);
this.assertEqual( SubFoo.prot(), val,
"Protected static methods can be overridden by subtypes"
);
},
/**
* This tests very closely to the implementation, which is not good.
* However, it's important to protecting the data. The accessor method works
* off of context, so it's important to ensure that the data will remain
* encapsulated if the user attempts to be tricky and bind to a supertype.
*/
'Cannot exploit accessor method to gain access to parent private props':
function()
{
var Foo = this.builder.build(
{
'private static foo': 'bar',
} ),
SubFoo = this.builder.build( Foo,
{
'public static getParentPrivate': function()
{
return this.$.call( Foo, 'foo' );
}
} )
;
this.assertEqual( SubFoo.getParentPrivate(), undefined,
"Cannot exploit accses modifier to gain access to parent private props"
);
},
/**
* Static members cannot be overridden. Instead, static members can be
* *hidden* if a member of the same name is defined by a subtype.
*/
'Cannot override static members': function()
{
var val_orig = 'foobaz',
val = 'foobar',
Foo = this.builder.build(
{
'public static prop': val_orig,
'public static foo': function()
{
return this.bar();
},
'public static bar': function()
{
return val_orig;
},
'public static baz': function()
{
return this.$( 'prop' );
},
} ),
SubFoo = this.builder.build( Foo,
{
'public static prop': val,
// override parent static method (this is truly overriding, not
// hiding)
'public static bar': function()
{
return val;
},
'public static getProp': function()
{
return this.$( 'prop' );
}
} )
;
// cannot override
this.assertNotEqual( SubFoo.foo(), val,
"System does not support overriding static methods"
);
this.assertNotEqual( SubFoo.baz(), val,
"System does not support overriding static properties"
);
// but we can hide them
this.assertEqual( SubFoo.bar(), val,
"System supports static method hiding"
);
this.assertEqual( SubFoo.getProp(), val,
"System supports static property hiding"
);
},
/**
* Since members are statically bound, calls to parent methods should retain
* access to their private members.
*/
'Calls to parent static methods retain private member access': function()
{
var val = 'foobar',
Foo = this.builder.build(
{
'private static _priv': val,
'public static getPriv': function()
{
return this.$('_priv');
},
} ),
SubFoo = this.builder.build( Foo,
{
'public static getPriv2': function()
{
return this.getPriv();
},
} )
;
this.assertEqual( SubFoo.getPriv(), val,
'Calls to parent static methods should retain access to their own ' +
'private members when called externally'
);
this.assertEqual( SubFoo.getPriv2(), val,
'Calls to parent static methods should retain access to their own ' +
'private members when called internally'
);
},
} );