2011-01-27 22:35:40 -05:00
|
|
|
/**
|
|
|
|
* Tests class member visibility (public, private, protected)
|
|
|
|
*
|
2013-12-20 01:11:26 -05:00
|
|
|
* Copyright (C) 2011, 2013 Mike Gerwitz
|
2011-01-27 22:35:40 -05:00
|
|
|
*
|
|
|
|
* This file is part of ease.js.
|
|
|
|
*
|
|
|
|
* ease.js is free software: you can redistribute it and/or modify it under the
|
Relicensed under the GPLv3+
This project was originally LGPLv+-licensed to encourage its use in a community
that is largely copyleft-phobic. After further reflection, that was a mistake,
as adoption is not the important factor here---software freedom is.
When submitting ease.js to the GNU project, it was asked if I would be willing
to relicense it under the GPLv3+; I agreed happily, because there is no reason
why we should provide proprietary software any sort of edge. Indeed, proprietary
JavaScript is a huge problem since it is automatically downloaded on the user's
PC generally without them even knowing, and is a current focus for the FSF. As
such, to remain firm in our stance against proprietary JavaScript, relicensing
made the most sense for GNU.
This is likely to upset current users of ease.js. I am not sure of their
number---I have only seen download counts periodically on npmjs.org---but I know
there are at least a small number. These users are free to continue using the
previous LGPL'd releases, but with the understanding that there will be no
further maintenance (not even bug fixes). If possible, users should use the
GPL-licensed versions and release their software as free software.
Here comes GNU ease.js.
2013-12-20 01:00:35 -05:00
|
|
|
* terms of the GNU General Public License as published by the Free Software
|
|
|
|
* Foundation, either version 3 of the License, or (at your option) any later
|
|
|
|
* version.
|
2011-01-27 22:35:40 -05:00
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
Relicensed under the GPLv3+
This project was originally LGPLv+-licensed to encourage its use in a community
that is largely copyleft-phobic. After further reflection, that was a mistake,
as adoption is not the important factor here---software freedom is.
When submitting ease.js to the GNU project, it was asked if I would be willing
to relicense it under the GPLv3+; I agreed happily, because there is no reason
why we should provide proprietary software any sort of edge. Indeed, proprietary
JavaScript is a huge problem since it is automatically downloaded on the user's
PC generally without them even knowing, and is a current focus for the FSF. As
such, to remain firm in our stance against proprietary JavaScript, relicensing
made the most sense for GNU.
This is likely to upset current users of ease.js. I am not sure of their
number---I have only seen download counts periodically on npmjs.org---but I know
there are at least a small number. These users are free to continue using the
previous LGPL'd releases, but with the understanding that there will be no
further maintenance (not even bug fixes). If possible, users should use the
GPL-licensed versions and release their software as free software.
Here comes GNU ease.js.
2013-12-20 01:00:35 -05:00
|
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
|
|
* more details.
|
2011-01-27 22:35:40 -05:00
|
|
|
*
|
Relicensed under the GPLv3+
This project was originally LGPLv+-licensed to encourage its use in a community
that is largely copyleft-phobic. After further reflection, that was a mistake,
as adoption is not the important factor here---software freedom is.
When submitting ease.js to the GNU project, it was asked if I would be willing
to relicense it under the GPLv3+; I agreed happily, because there is no reason
why we should provide proprietary software any sort of edge. Indeed, proprietary
JavaScript is a huge problem since it is automatically downloaded on the user's
PC generally without them even knowing, and is a current focus for the FSF. As
such, to remain firm in our stance against proprietary JavaScript, relicensing
made the most sense for GNU.
This is likely to upset current users of ease.js. I am not sure of their
number---I have only seen download counts periodically on npmjs.org---but I know
there are at least a small number. These users are free to continue using the
previous LGPL'd releases, but with the understanding that there will be no
further maintenance (not even bug fixes). If possible, users should use the
GPL-licensed versions and release their software as free software.
Here comes GNU ease.js.
2013-12-20 01:00:35 -05:00
|
|
|
* You should have received a copy of the GNU General Public License along with
|
|
|
|
* this program. If not, see <http://www.gnu.org/licenses/>.
|
2011-01-27 22:35:40 -05:00
|
|
|
*
|
|
|
|
* @author Mike Gerwitz
|
|
|
|
*/
|
|
|
|
|
2011-03-18 23:55:56 -04:00
|
|
|
var common = require( './common' ),
|
|
|
|
Class = common.require( 'class' ),
|
|
|
|
Interface = common.require( 'interface' ),
|
2011-08-13 23:58:08 -04:00
|
|
|
util = common.require( 'util' ),
|
2011-01-27 22:35:40 -05:00
|
|
|
|
|
|
|
pub = 'foo',
|
|
|
|
prot = 'bar',
|
|
|
|
priv = 'baz',
|
|
|
|
|
2011-03-02 20:43:24 -05:00
|
|
|
pubf = function() { return pub; },
|
|
|
|
protf = function() { return prot; },
|
|
|
|
privf = function() { return priv; },
|
2011-01-27 22:39:52 -05:00
|
|
|
|
2011-01-27 22:35:40 -05:00
|
|
|
// new anonymous class instance
|
2011-03-07 00:14:43 -05:00
|
|
|
Foo = Class.extend( {
|
2011-01-27 22:35:40 -05:00
|
|
|
'public pub': pub,
|
|
|
|
'protected peeps': prot,
|
|
|
|
'private parts': priv,
|
2011-01-27 22:39:52 -05:00
|
|
|
|
|
|
|
'public pubf': pubf,
|
|
|
|
'protected protf': protf,
|
|
|
|
'private privf': privf,
|
2011-03-02 20:43:24 -05:00
|
|
|
|
2011-03-10 12:19:39 -05:00
|
|
|
|
2011-06-08 01:11:53 -04:00
|
|
|
'virtual public getProp': function( name )
|
2011-03-02 20:43:24 -05:00
|
|
|
{
|
|
|
|
// return property, allowing us to break encapsulation for
|
|
|
|
// protected/private properties (for testing purposes)
|
|
|
|
return this[ name ];
|
|
|
|
},
|
2011-03-02 23:21:10 -05:00
|
|
|
|
|
|
|
|
2011-03-13 15:39:14 -04:00
|
|
|
/**
|
|
|
|
* Does the same as the above, but we won't override this one
|
|
|
|
*/
|
|
|
|
'public nonOverrideGetProp': function( name )
|
|
|
|
{
|
|
|
|
return this[ name ];
|
|
|
|
},
|
|
|
|
|
|
|
|
|
2011-03-02 23:21:10 -05:00
|
|
|
/**
|
|
|
|
* Allows us to set a value from within the class
|
|
|
|
*/
|
|
|
|
'public setValue': function( name, value )
|
|
|
|
{
|
|
|
|
this[ name ] = value;
|
|
|
|
},
|
2011-03-10 12:19:39 -05:00
|
|
|
|
|
|
|
|
|
|
|
'public getSelf': function()
|
|
|
|
{
|
|
|
|
return this;
|
|
|
|
},
|
2011-03-10 12:40:55 -05:00
|
|
|
|
|
|
|
|
2011-06-08 01:11:53 -04:00
|
|
|
'virtual public getSelfOverride': function()
|
2011-03-10 12:40:55 -05:00
|
|
|
{
|
|
|
|
// override me
|
|
|
|
},
|
2011-03-13 04:51:00 -04:00
|
|
|
|
|
|
|
|
|
|
|
'public getPrivProp': function()
|
|
|
|
{
|
|
|
|
return this.parts;
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
'public invokePriv': function()
|
|
|
|
{
|
|
|
|
return this._priv();
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
'private _priv': function()
|
|
|
|
{
|
|
|
|
return priv;
|
|
|
|
},
|
2011-03-07 00:14:43 -05:00
|
|
|
}),
|
|
|
|
|
|
|
|
// instance of Foo
|
|
|
|
foo = Foo(),
|
|
|
|
|
|
|
|
// subtype
|
2011-03-10 12:40:55 -05:00
|
|
|
SubFoo = Foo.extend({
|
2011-03-13 04:51:00 -04:00
|
|
|
'private _pfoo': 'baz',
|
|
|
|
|
2011-08-04 00:32:10 -04:00
|
|
|
'override public getSelfOverride': function()
|
2011-03-10 12:40:55 -05:00
|
|
|
{
|
|
|
|
// return this from overridden method
|
|
|
|
return this;
|
|
|
|
},
|
2011-03-13 04:51:00 -04:00
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* We have to override this so that 'this' is not bound to the supertype
|
|
|
|
*/
|
2011-08-04 00:32:10 -04:00
|
|
|
'override public getProp': function( name )
|
2011-03-13 04:51:00 -04:00
|
|
|
{
|
|
|
|
// return property, allowing us to break encapsulation for
|
|
|
|
// protected/private properties (for testing purposes)
|
|
|
|
return this[ name ];
|
|
|
|
},
|
2011-03-13 15:39:14 -04:00
|
|
|
|
|
|
|
|
|
|
|
'private myOwnPrivateFoo': function() {},
|
2011-03-10 12:40:55 -05:00
|
|
|
}),
|
2011-03-13 04:51:00 -04:00
|
|
|
sub_foo = SubFoo(),
|
|
|
|
|
|
|
|
sub_sub_foo = SubFoo.extend( {} )()
|
2011-03-07 00:14:43 -05:00
|
|
|
;
|
2011-01-27 22:35:40 -05:00
|
|
|
|
|
|
|
|
2011-11-02 22:04:53 -04:00
|
|
|
require( 'common' ).testCase(
|
2011-01-27 22:35:40 -05:00
|
|
|
{
|
2011-11-02 22:04:53 -04:00
|
|
|
/**
|
|
|
|
* Public members are the only members added to the instance's prototype to
|
|
|
|
* be accessible externally
|
|
|
|
*/
|
|
|
|
'Public members are accessible externally': function()
|
2011-03-06 22:43:14 -05:00
|
|
|
{
|
2011-11-02 22:04:53 -04:00
|
|
|
this.assertEqual(
|
|
|
|
foo.pub,
|
|
|
|
pub,
|
|
|
|
"Public properties are accessible via public interface"
|
|
|
|
);
|
|
|
|
|
|
|
|
this.assertEqual(
|
|
|
|
foo.pubf(),
|
|
|
|
pub,
|
|
|
|
"Public methods are accessible via public interface"
|
|
|
|
);
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* For reasons that are discussed in the next test (writing to public
|
|
|
|
* properties), we need to make sure public members are available
|
|
|
|
* internally. Actually, we don't need to test public methods, really, but
|
|
|
|
* it's in there for good measure. Who knows what bugs may be introduced in
|
|
|
|
* the future.
|
|
|
|
*
|
|
|
|
* This ensures that the getter is properly proxying the value to us.
|
|
|
|
*/
|
|
|
|
'Public members are accessible internally': function()
|
2011-03-07 00:14:43 -05:00
|
|
|
{
|
2011-11-02 22:04:53 -04:00
|
|
|
this.assertEqual(
|
|
|
|
foo.getProp( 'pub' ),
|
|
|
|
pub,
|
|
|
|
"Public properties are accessible internally"
|
|
|
|
);
|
|
|
|
|
|
|
|
this.assertEqual(
|
|
|
|
foo.getProp( 'pubf' )(),
|
|
|
|
pub,
|
|
|
|
"Public methods are accessible internally"
|
|
|
|
);
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This may sound like an odd test, but it's actually very important. Due to
|
|
|
|
* how private/protected members are implemented, it compromises public
|
|
|
|
* members. In fact, public members would not work internally without what
|
|
|
|
* is essentially a proxy via setters.
|
|
|
|
*
|
|
|
|
* This test is to ensure that the setter is properly forwarding writes to
|
|
|
|
* the object within the prototype chain containing the public values.
|
|
|
|
* Otherwise, setting the value would simply mask it in the prototype chain.
|
|
|
|
* The value would appear to have changed internally, but when accessed
|
|
|
|
* externally, the value would still be the same. That would obviously be a
|
|
|
|
* problem ;)
|
|
|
|
*/
|
|
|
|
'Public properties are writable internally': function()
|
2011-03-13 21:47:40 -04:00
|
|
|
{
|
2011-11-02 22:04:53 -04:00
|
|
|
var val = 'moomookittypoo';
|
|
|
|
|
|
|
|
// start by setting the value
|
|
|
|
foo.setValue( 'pub', val );
|
|
|
|
|
|
|
|
// we should see that change internally...
|
|
|
|
this.assertEqual(
|
|
|
|
foo.getProp( 'pub' ),
|
|
|
|
val,
|
|
|
|
"Setting the value of a public property internally should be " +
|
|
|
|
"observable /internally/"
|
|
|
|
);
|
|
|
|
|
|
|
|
// ...as well as externally
|
|
|
|
this.assertEqual(
|
|
|
|
foo.pub,
|
|
|
|
val,
|
|
|
|
"Setting the value of a public property internally should be " +
|
|
|
|
"observable /externally/"
|
|
|
|
);
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
'Protected and private members are not accessible externally': function()
|
2011-03-18 23:42:07 -04:00
|
|
|
{
|
2011-11-02 22:04:53 -04:00
|
|
|
// browsers that do not support the property proxy will not support
|
|
|
|
// encapsulating properties
|
|
|
|
if ( util.definePropertyFallback() )
|
2011-03-18 23:42:07 -04:00
|
|
|
{
|
2011-11-02 22:04:53 -04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.assertEqual(
|
|
|
|
foo.peeps,
|
|
|
|
undefined,
|
|
|
|
"Protected properties are inaccessible via public interface"
|
|
|
|
);
|
|
|
|
|
|
|
|
this.assertEqual(
|
|
|
|
foo.parts,
|
|
|
|
undefined,
|
|
|
|
"Private properties are inaccessible via public interface"
|
|
|
|
);
|
|
|
|
|
|
|
|
this.assertEqual(
|
|
|
|
foo.protf,
|
|
|
|
undefined,
|
|
|
|
"Protected methods are inaccessible via public interface"
|
|
|
|
);
|
|
|
|
|
|
|
|
this.assertEqual(
|
|
|
|
foo.privf,
|
|
|
|
undefined,
|
|
|
|
"Private methods are inaccessible via public interface"
|
|
|
|
);
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Protected members should be accessible from within class methods
|
|
|
|
*/
|
|
|
|
'Protected members are accessible internally': function()
|
2011-10-22 01:07:07 -04:00
|
|
|
{
|
2011-11-02 22:04:53 -04:00
|
|
|
this.assertEqual(
|
|
|
|
foo.getProp( 'peeps' ),
|
|
|
|
prot,
|
|
|
|
"Protected properties are available internally"
|
|
|
|
);
|
|
|
|
|
|
|
|
// invoke rather than checking for equality, because the method may be
|
|
|
|
// wrapped
|
|
|
|
this.assertEqual(
|
|
|
|
foo.getProp( 'protf' )(),
|
|
|
|
prot,
|
|
|
|
"Protected methods are available internally"
|
|
|
|
);
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Private members should be accessible from within class methods
|
|
|
|
*/
|
|
|
|
'Private members are accessible internally': function()
|
2011-03-18 23:42:07 -04:00
|
|
|
{
|
2011-11-02 22:04:53 -04:00
|
|
|
this.assertEqual(
|
|
|
|
foo.getProp( 'parts' ),
|
|
|
|
priv,
|
|
|
|
"Private properties are available internally"
|
|
|
|
);
|
|
|
|
|
|
|
|
// invoke rather than checking for equality, because the method may be
|
|
|
|
// wrapped
|
|
|
|
this.assertEqual(
|
|
|
|
foo.getProp( 'privf' )(),
|
|
|
|
priv,
|
|
|
|
"Private methods are available internally"
|
|
|
|
);
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Inheritance 101; protected members should be available to subtypes
|
|
|
|
*/
|
|
|
|
'Protected members are inherited from parent': function()
|
2011-03-18 23:42:07 -04:00
|
|
|
{
|
2011-11-02 22:04:53 -04:00
|
|
|
this.assertEqual(
|
|
|
|
sub_foo.getProp( 'peeps' ),
|
|
|
|
prot,
|
|
|
|
"Protected properties are available to subtypes"
|
|
|
|
);
|
|
|
|
|
|
|
|
// invoke rather than checking for equality, because the method may be
|
|
|
|
// wrapped
|
|
|
|
this.assertEqual(
|
|
|
|
sub_foo.getProp( 'protf' )(),
|
|
|
|
prot,
|
|
|
|
"Protected methods are available to subtypes"
|
|
|
|
);
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Interface 101-2: We do not want private members to be available to
|
|
|
|
* subtypes.
|
|
|
|
*/
|
|
|
|
'Private members of supertypes are inaccessible to subtypes': function()
|
2011-03-18 23:42:07 -04:00
|
|
|
{
|
2011-11-02 22:04:53 -04:00
|
|
|
// browsers that do not support the property proxy will not support
|
|
|
|
// encapsulating properties
|
|
|
|
if ( util.definePropertyFallback() )
|
2011-03-18 23:42:07 -04:00
|
|
|
{
|
2011-11-02 22:04:53 -04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.assertEqual(
|
|
|
|
sub_foo.getProp( 'parts' ),
|
|
|
|
undefined,
|
|
|
|
"Private properties of supertypes should be unavailable to subtypes"
|
|
|
|
);
|
|
|
|
|
|
|
|
// invoke rather than checking for equality, because the method may be
|
|
|
|
// wrapped
|
|
|
|
this.assertEqual(
|
|
|
|
sub_foo.getProp( 'privf' ),
|
|
|
|
undefined,
|
|
|
|
"Private methods of supertypes should be unavailable to subtypes"
|
|
|
|
);
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* For good measure, let's make sure we didn't screw anything up. To ensure
|
|
|
|
* that the same object isn't being passed around to subtypes, ensure that
|
|
|
|
* multiple class instances do not share prototypes.
|
|
|
|
*/
|
|
|
|
'Protected members are not shared between class instances': function()
|
2011-03-18 23:42:07 -04:00
|
|
|
{
|
2011-11-02 22:04:53 -04:00
|
|
|
var val = 'foobar';
|
|
|
|
|
|
|
|
foo.setValue( 'prot', val );
|
|
|
|
|
|
|
|
// ensure that class instances do not share values (ensuring the same
|
|
|
|
// object isn't somehow being passed around)
|
|
|
|
this.assertNotEqual(
|
|
|
|
sub_foo.getProp( 'prot' ),
|
|
|
|
val,
|
|
|
|
"Class instances do not share protected values (subtype)"
|
|
|
|
);
|
|
|
|
|
|
|
|
// do the same for multiple instances of the same type
|
|
|
|
var sub_foo2 = SubFoo();
|
|
|
|
sub_foo2.setValue( 'prot', val );
|
|
|
|
|
|
|
|
this.assertNotEqual(
|
|
|
|
sub_foo.getProp( 'prot' ),
|
|
|
|
val,
|
|
|
|
"Class instances do not share protected values (same type)"
|
|
|
|
);
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* When a method is called, 'this' is bound to the property object
|
|
|
|
* containing private and protected members. Returning 'this' would
|
|
|
|
* therefore be a very bad thing. Not only would it break encapsulation, but
|
|
|
|
* it would likely have other problems down the road.
|
|
|
|
*
|
|
|
|
* Therefore, we have to check the return value of the method. If the return
|
|
|
|
* value is the property object that it was bound to, we need to replace the
|
|
|
|
* return value with the actual class instance. This allows us to
|
|
|
|
* transparently enforce encapsulation. How sweet is that?
|
|
|
|
*/
|
|
|
|
'Returning self from method should return instance not prop obj': function()
|
2011-03-18 23:42:07 -04:00
|
|
|
{
|
2011-11-02 22:04:53 -04:00
|
|
|
this.assertDeepEqual(
|
|
|
|
foo.getSelf(),
|
|
|
|
foo,
|
|
|
|
"Returning 'this' from a method should return instance of self"
|
|
|
|
);
|
|
|
|
|
|
|
|
// what happens in the case of inheritance?
|
|
|
|
this.assertDeepEqual(
|
|
|
|
sub_foo.getSelf(),
|
|
|
|
sub_foo,
|
|
|
|
"Returning 'this' from a super method should return the subtype"
|
|
|
|
);
|
|
|
|
|
|
|
|
// finally, overridden methods should still return the instance
|
|
|
|
this.assertDeepEqual(
|
|
|
|
sub_foo.getSelfOverride(),
|
|
|
|
sub_foo,
|
|
|
|
"Returning 'this' from a overridden method should return subtype"
|
|
|
|
);
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This one's a particularly nasty bug that snuck up on me. Private members
|
|
|
|
* should not be accessible to subtypes; that's a given. However, they need
|
|
|
|
* to be accessible to the parent methods. For example, let's say class Foo
|
|
|
|
* contains public method bar(), which invokes private method _baz(). This
|
|
|
|
* is perfectly legal. Then SubFoo extends Foo, but does not override method
|
|
|
|
* bar(). Invoking method bar() should still be able to invoke private
|
|
|
|
* method _baz(), because, from the perspective of the parent class, that
|
|
|
|
* operation is perfectly legal.
|
|
|
|
*
|
|
|
|
* The resolution of this bug required a slight system redesign. The
|
|
|
|
* short-term fix was to declare any needed private members are protected,
|
|
|
|
* so that they were accessible by the subtype.
|
|
|
|
*/
|
|
|
|
'Parent methods can access private members of parent': function()
|
|
|
|
{
|
|
|
|
// properties
|
|
|
|
this.assertEqual(
|
|
|
|
sub_foo.getPrivProp(),
|
|
|
|
priv,
|
|
|
|
"Parent methods should have access to the private properties of " +
|
|
|
|
"the parent"
|
|
|
|
);
|
|
|
|
|
|
|
|
// methods
|
|
|
|
this.assertEqual(
|
|
|
|
sub_foo.invokePriv(),
|
|
|
|
priv,
|
|
|
|
"Parent methods should have access to the private methods of the " +
|
|
|
|
"parent"
|
|
|
|
);
|
|
|
|
|
|
|
|
// should apply to super-supertypes too
|
|
|
|
this.assertEqual(
|
|
|
|
sub_sub_foo.getPrivProp(),
|
|
|
|
priv,
|
|
|
|
"Parent methods should have access to the private properties of " +
|
|
|
|
"the parent (2)"
|
|
|
|
);
|
|
|
|
this.assertEqual(
|
|
|
|
sub_sub_foo.invokePriv(),
|
|
|
|
priv,
|
|
|
|
"Parent methods should have access to the private methods of the " +
|
|
|
|
"parent (2)"
|
|
|
|
);
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* When a parent method is invoked, the parent should not be given access to
|
|
|
|
* the private members of the invoking subtype. Why?
|
|
|
|
*
|
|
|
|
* This is not a matter of whether or not this is possible to do. In fact
|
|
|
|
* it's relatively simple to implement. The issue is whether or not it makes
|
|
|
|
* sense. Consider a compiled language. Let's say Foo and SubFoo (as
|
|
|
|
* defined in this test case) were written in C++. Should Foo have access to
|
|
|
|
* a private property on SubFoo when it is overridden?
|
|
|
|
*
|
|
|
|
* No - that doesn't make sense. The private member is not a member of Foo
|
|
|
|
* and therefore Foo would fail to even compile. Alright, but we don't have
|
|
|
|
* such a restriction in our case. So why not implement it?
|
|
|
|
*
|
|
|
|
* Proponents of such an implementation are likely thinking of the act of
|
|
|
|
* inheriting methods as a copy/paste type of scenario. If we inherit public
|
|
|
|
* method baz(), and it were a copy/paste type of situation, then surely
|
|
|
|
* baz() would have access to all of SubFoo's private members. But that is
|
|
|
|
* not the case. Should baz() be defined as a member of Foo, then its scope
|
|
|
|
* is restricted to Foo and its supertypes. That is not how OO works. It is
|
|
|
|
* /not/ copy/paste. It is inheriting functionality.
|
|
|
|
*/
|
|
|
|
'Parents should not have access to private members of subtypes': function()
|
|
|
|
{
|
|
|
|
// browsers that do not support the property proxy will not support
|
|
|
|
// encapsulating properties
|
|
|
|
if ( util.definePropertyFallback() )
|
2011-03-18 23:42:07 -04:00
|
|
|
{
|
2011-11-02 22:04:53 -04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// property
|
|
|
|
this.assertEqual(
|
|
|
|
sub_foo.nonOverrideGetProp( '_pfoo' ),
|
|
|
|
undefined,
|
|
|
|
"Parent should not have access to private properties of subtype " +
|
|
|
|
"whena parent method is invoked"
|
|
|
|
);
|
|
|
|
|
|
|
|
// member
|
|
|
|
this.assertEqual(
|
|
|
|
sub_foo.nonOverrideGetProp( '_myOwnPrivateFoo' ),
|
|
|
|
undefined,
|
|
|
|
"Parent should not have access to private methods of subtype " +
|
|
|
|
"when a parent method is invoked"
|
|
|
|
);
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Visibility escalation (protected -> private) should be permitted
|
|
|
|
*/
|
|
|
|
'Can escalate member visibility': function()
|
2011-03-18 23:42:07 -04:00
|
|
|
{
|
2011-11-02 22:04:53 -04:00
|
|
|
// escalate
|
|
|
|
this.assertDoesNotThrow( function()
|
2011-03-18 23:42:07 -04:00
|
|
|
{
|
2011-11-02 22:04:53 -04:00
|
|
|
Class(
|
2011-03-18 23:49:28 -04:00
|
|
|
{
|
2011-11-02 22:04:53 -04:00
|
|
|
'protected foo': 'bar',
|
|
|
|
'virtual protected baz': function() {},
|
|
|
|
} ).extend( {
|
|
|
|
'public foo': 'bar',
|
|
|
|
'override public baz': function() {},
|
|
|
|
} );
|
|
|
|
}, Error, "Can escalate visibility of subtype members" );
|
|
|
|
|
|
|
|
// same level of visibility
|
|
|
|
this.assertDoesNotThrow( function()
|
2011-03-18 23:49:28 -04:00
|
|
|
{
|
2011-11-02 22:04:53 -04:00
|
|
|
Class(
|
2011-03-18 23:49:28 -04:00
|
|
|
{
|
2011-11-02 22:04:53 -04:00
|
|
|
'protected foo': 'bar',
|
|
|
|
'virtual protected baz': function() {},
|
|
|
|
} ).extend( {
|
|
|
|
'protected foo': 'bar',
|
|
|
|
'override protected baz': function() {},
|
|
|
|
} );
|
|
|
|
}, Error, "Can retain level of visibility for subtype members" );
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* We should /not/ be able to de-escalate member visibility
|
|
|
|
* (public -> {protected,private}
|
|
|
|
*/
|
|
|
|
'Cannot de-escalate member visibility': function()
|
2011-03-18 23:55:56 -04:00
|
|
|
{
|
2011-11-02 22:04:53 -04:00
|
|
|
// public -> protected
|
|
|
|
this.assertThrows( function()
|
2011-04-01 06:28:45 -04:00
|
|
|
{
|
2011-11-02 22:04:53 -04:00
|
|
|
Class(
|
2011-04-01 06:28:45 -04:00
|
|
|
{
|
2011-11-02 22:04:53 -04:00
|
|
|
'public foo': 'bar',
|
|
|
|
} ).extend( {
|
|
|
|
'protected foo': 'bar',
|
|
|
|
} );
|
|
|
|
}, Error, "Cannot de-escalate visibility of sub-props to protected" );
|
|
|
|
|
|
|
|
this.assertThrows( function()
|
2011-04-01 06:28:45 -04:00
|
|
|
{
|
2011-11-02 22:04:53 -04:00
|
|
|
Class(
|
2011-04-01 06:28:45 -04:00
|
|
|
{
|
2011-11-02 22:04:53 -04:00
|
|
|
'virtual public baz': function() {},
|
|
|
|
} ).extend( {
|
|
|
|
'protected baz': function() {},
|
|
|
|
} );
|
|
|
|
}, Error, "Cannot de-escalate visibility of sub-methods to protected" );
|
2011-04-01 06:28:45 -04:00
|
|
|
|
2011-04-02 10:58:26 -04:00
|
|
|
|
2011-11-02 22:04:53 -04:00
|
|
|
// public -> private
|
|
|
|
this.assertThrows( function()
|
2011-04-02 10:58:26 -04:00
|
|
|
{
|
2011-11-02 22:04:53 -04:00
|
|
|
Class(
|
2011-04-02 10:58:26 -04:00
|
|
|
{
|
2011-11-02 22:04:53 -04:00
|
|
|
'public foo': 'bar',
|
|
|
|
} ).extend( {
|
|
|
|
'private foo': 'bar',
|
|
|
|
} );
|
|
|
|
}, Error, "Cannot de-escalate visibility of subtype props to private" );
|
|
|
|
|
|
|
|
this.assertThrows( function()
|
2011-04-02 10:58:26 -04:00
|
|
|
{
|
2011-11-02 22:04:53 -04:00
|
|
|
Class(
|
2011-04-02 10:58:26 -04:00
|
|
|
{
|
2011-11-02 22:04:53 -04:00
|
|
|
'virtual public baz': function() {},
|
|
|
|
} ).extend( {
|
|
|
|
'private baz': function() {},
|
|
|
|
} );
|
|
|
|
}, Error, "Cannot de-escalate visibility of sub-methods to private" );
|
2011-04-02 10:58:26 -04:00
|
|
|
|
|
|
|
|
2011-11-02 22:04:53 -04:00
|
|
|
// protected -> private
|
|
|
|
this.assertThrows( function()
|
|
|
|
{
|
|
|
|
Class(
|
|
|
|
{
|
|
|
|
'protected foo': 'bar',
|
|
|
|
} ).extend( {
|
|
|
|
'private foo': 'bar',
|
|
|
|
} );
|
|
|
|
}, Error, "Cannot de-escalate visibility of sub-props to private2" );
|
2011-04-13 14:48:20 -04:00
|
|
|
|
2011-11-02 22:04:53 -04:00
|
|
|
this.assertThrows( function()
|
2011-04-13 14:48:20 -04:00
|
|
|
{
|
2011-11-02 22:04:53 -04:00
|
|
|
Class(
|
|
|
|
{
|
|
|
|
'virtual protected baz': function() {},
|
|
|
|
} ).extend( {
|
|
|
|
'private baz': function() {},
|
|
|
|
} );
|
|
|
|
}, Error, "Cannot de-escalate visibility of sub-methods to private2" );
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* With the visibility implementation, it's possible that __super() will not
|
|
|
|
* work properly with protected methods. This is because of the override
|
|
|
|
* lookup process (which hopefully was fixed in the commit before this test
|
|
|
|
* was originally introduced: ce736bea).
|
|
|
|
*/
|
|
|
|
'Calling super method works properly with protected methods': function()
|
|
|
|
{
|
|
|
|
var val = 'foobar',
|
|
|
|
result = Class( {
|
|
|
|
'virtual protected foo': function()
|
|
|
|
{
|
|
|
|
return val;
|
|
|
|
},
|
|
|
|
} ).extend(
|
|
|
|
{
|
|
|
|
// we override to public just so we can call it externally
|
|
|
|
'override public foo': function()
|
|
|
|
{
|
|
|
|
return this.__super();
|
|
|
|
},
|
|
|
|
} )().foo();
|
|
|
|
|
|
|
|
this.assertEqual( result, val,
|
|
|
|
"__super() calls work with protected overrides"
|
|
|
|
);
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Concrete implementations of interfaces should have to follow the same
|
|
|
|
* visibility de-escalation rules as defined in the above tests (otherwise,
|
|
|
|
* that defeats the purpose of an interface). In other words, they must be
|
|
|
|
* public.
|
|
|
|
*/
|
|
|
|
'Visibility de-escalation rulse apply to interfaces': function()
|
|
|
|
{
|
|
|
|
this.assertThrows( function()
|
2011-04-13 14:48:20 -04:00
|
|
|
{
|
2011-11-02 22:04:53 -04:00
|
|
|
Class.implement( Interface( { 'abstract public foo': [] } ) )
|
|
|
|
.extend(
|
|
|
|
{
|
|
|
|
// should throw an exception; visibility de-escalation
|
|
|
|
'protected foo': function() {},
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}, Error, "Cannot de-escalate visibility for interface members" );
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Due to the way the property object is laid atop of the public members, we
|
|
|
|
* need to ensure that protected methods' functionality can /actually/ be
|
|
|
|
* overridden, since the protected method is higher in the prototype chain
|
|
|
|
* and therefore will be accessed before the public method.
|
|
|
|
*
|
|
|
|
* We don't care about private -> protected, because that's not possible
|
|
|
|
* through inheritance.
|
|
|
|
*/
|
|
|
|
'Can override protected method functionality with public': function()
|
|
|
|
{
|
|
|
|
// get the result of invoking overridden foo()
|
|
|
|
var result = Class(
|
|
|
|
{
|
|
|
|
'virtual protected foo': function()
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
} ).extend(
|
|
|
|
{
|
|
|
|
// override and escalate visibility of method foo()
|
|
|
|
'override public foo': function()
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
} )().foo();
|
|
|
|
|
|
|
|
// if the override was successful, we'll be able to invoke the
|
|
|
|
// overridden method
|
|
|
|
this.assertEqual( result, true,
|
|
|
|
"Can properly override protected methods with public"
|
|
|
|
);
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* There was an issue where the private property object was not proxying
|
|
|
|
* values to the true protected values. This would mean that when the parent
|
|
|
|
* initialized protected values, those values would be unavailable to the
|
|
|
|
* subtype. Instead, the value available to the subtype was the value that
|
|
|
|
* was assigned as the default value in the class definition.
|
|
|
|
*/
|
|
|
|
'Protected values are available to subtypes when set by parent': function()
|
|
|
|
{
|
|
|
|
var expected = 5,
|
|
|
|
result = Class(
|
2011-04-13 14:48:20 -04:00
|
|
|
{
|
2011-11-02 22:04:53 -04:00
|
|
|
'protected val': 0,
|
2011-04-13 14:48:20 -04:00
|
|
|
|
2011-11-02 22:04:53 -04:00
|
|
|
'public __construct': function()
|
|
|
|
{
|
|
|
|
this.val = expected;
|
|
|
|
},
|
|
|
|
} ).extend(
|
|
|
|
{
|
|
|
|
'public getVal': function()
|
|
|
|
{
|
|
|
|
return this.val;
|
|
|
|
},
|
|
|
|
} )().getVal();
|
|
|
|
|
|
|
|
this.assertEqual( result, expected,
|
|
|
|
"Subtypes should have acess to protected properties values set " +
|
|
|
|
"by super methods"
|
|
|
|
);
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* There was a bug introduced when we prevented protected members from
|
|
|
|
* overriding public (since in the prototype chain, protected members are
|
|
|
|
* laid atop public, and this cannot change). This bug would disallow
|
|
|
|
* protected members from being overridden by other protected members.
|
|
|
|
*
|
|
|
|
* This test is both a proof and a regression test.
|
|
|
|
*/
|
|
|
|
'Can properly override protected with protected': function()
|
|
|
|
{
|
|
|
|
var val = 'foobar',
|
|
|
|
result = Class(
|
|
|
|
{
|
|
|
|
'virtual protected foo': function() {},
|
|
|
|
} ).extend(
|
2011-04-13 14:48:20 -04:00
|
|
|
{
|
2011-11-02 22:04:53 -04:00
|
|
|
// provide concrete implementation
|
|
|
|
'override protected foo': function()
|
|
|
|
{
|
|
|
|
return val;
|
|
|
|
},
|
|
|
|
|
|
|
|
'public doFoo': function()
|
|
|
|
{
|
|
|
|
return this.foo();
|
|
|
|
},
|
|
|
|
} )().doFoo();
|
|
|
|
;
|
|
|
|
|
|
|
|
// if everything worked as expected, the value of 'val' will have been
|
|
|
|
// returned and stored in 'result'
|
|
|
|
this.assertEqual( result, val,
|
|
|
|
"Protected methods can properly be overriden by another " +
|
|
|
|
"protected method"
|
|
|
|
);
|
|
|
|
},
|
|
|
|
} );
|
2011-04-13 14:48:20 -04:00
|
|
|
|