2010-12-29 23:33:30 -05:00
|
|
|
/**
|
2010-12-30 09:54:53 -05:00
|
|
|
* Tests class interface implement method
|
2010-12-29 23:33:30 -05:00
|
|
|
*
|
2013-12-20 01:11:26 -05:00
|
|
|
* Copyright (C) 2010, 2011, 2013 Mike Gerwitz
|
2010-12-29 23:33:30 -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.
|
2010-12-29 23:33:30 -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.
|
2010-12-29 23:33:30 -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/>.
|
2010-12-29 23:33:30 -05:00
|
|
|
*
|
|
|
|
* @author Mike Gerwitz
|
|
|
|
*/
|
|
|
|
|
|
|
|
var common = require( './common' ),
|
|
|
|
assert = require( 'assert' ),
|
2011-05-22 13:57:56 -04:00
|
|
|
|
|
|
|
Class = common.require( 'class' ),
|
|
|
|
Interface = common.require( 'interface' ),
|
|
|
|
AbstractClass = common.require( 'class_abstract' )
|
|
|
|
;
|
2010-12-29 23:33:30 -05:00
|
|
|
|
|
|
|
|
2011-11-28 15:10:26 -05:00
|
|
|
// test with and without abstract keyword
|
2010-12-29 23:33:30 -05:00
|
|
|
var Type = Interface.extend( {
|
2011-01-11 19:03:30 -05:00
|
|
|
'abstract foo': [],
|
|
|
|
}),
|
2010-12-29 23:33:30 -05:00
|
|
|
|
2011-01-11 19:03:30 -05:00
|
|
|
Type2 = Interface.extend( {
|
2011-11-28 15:10:26 -05:00
|
|
|
foo2: [],
|
2011-01-11 19:03:30 -05:00
|
|
|
}),
|
2010-12-29 23:33:30 -05:00
|
|
|
|
2011-01-11 19:03:30 -05:00
|
|
|
Foo = {},
|
|
|
|
PlainFoo = Class.extend(),
|
|
|
|
PlainFoo2 = {}
|
|
|
|
;
|
2010-12-29 23:33:30 -05:00
|
|
|
|
|
|
|
|
2011-11-02 22:32:45 -04:00
|
|
|
require( 'common' ).testCase(
|
2011-01-11 19:03:30 -05:00
|
|
|
{
|
2011-11-02 22:32:45 -04:00
|
|
|
'Class exports contain implement method for no base class': function()
|
2011-01-04 00:37:54 -05:00
|
|
|
{
|
2011-11-02 22:32:45 -04:00
|
|
|
this.assertOk(
|
|
|
|
( Class.implement instanceof Function ),
|
|
|
|
"Class provides method to implement interfaces"
|
|
|
|
);
|
|
|
|
},
|
2011-01-11 19:03:30 -05:00
|
|
|
|
2011-03-05 02:59:21 -05:00
|
|
|
|
2011-11-02 22:32:45 -04:00
|
|
|
'Clsss object contains implement method for self as base': function()
|
2011-01-11 19:03:30 -05:00
|
|
|
{
|
2011-11-02 22:32:45 -04:00
|
|
|
this.assertOk(
|
|
|
|
( PlainFoo.implement instanceof Function ),
|
|
|
|
"Classes contain an implement() method"
|
|
|
|
);
|
|
|
|
},
|
2011-03-05 02:59:21 -05:00
|
|
|
|
2011-01-11 19:03:30 -05:00
|
|
|
|
2011-11-02 22:32:45 -04:00
|
|
|
'Can implement interface from an empty base': function()
|
|
|
|
{
|
|
|
|
this.assertDoesNotThrow( function()
|
|
|
|
{
|
|
|
|
Class.implement( Type, Type2 );
|
|
|
|
}, Error, "Class can implement interfaces" );
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Initially, the implement() method returned an abstract class. However, it
|
|
|
|
* doesn't make sense to create a class without any actual definition (and
|
|
|
|
* there's other implementation considerations that caused this route to be
|
|
|
|
* taken). One wouldn't do "class Foo implements Type", and not provide any
|
|
|
|
* body.
|
|
|
|
*
|
|
|
|
* Therefore, implement() should return nothing useful until extend() is
|
|
|
|
* called on it.
|
|
|
|
*/
|
|
|
|
'Result of implement is not usable as a class': function()
|
|
|
|
{
|
|
|
|
var result = Class.implement( Type );
|
2011-01-11 19:03:30 -05:00
|
|
|
|
2011-11-02 22:32:45 -04:00
|
|
|
this.assertEqual(
|
|
|
|
( Class.isClass( result ) ),
|
|
|
|
false,
|
|
|
|
"Result of implement operation on class is not usable as a Class"
|
|
|
|
);
|
|
|
|
},
|
2011-03-05 02:59:21 -05:00
|
|
|
|
2011-01-04 00:37:54 -05:00
|
|
|
|
2011-11-02 22:32:45 -04:00
|
|
|
/**
|
|
|
|
* As a consequence of the above, we must extend with an empty definition
|
|
|
|
* (base) in order to get our abstract class.
|
|
|
|
*/
|
|
|
|
'Abstract methods are copied into new class using empty base': function()
|
|
|
|
{
|
|
|
|
Foo = AbstractClass.implement( Type, Type2 ).extend( {} );
|
2011-01-04 00:37:54 -05:00
|
|
|
|
2011-11-02 22:32:45 -04:00
|
|
|
this.assertOk(
|
|
|
|
( ( Foo.prototype.foo instanceof Function )
|
|
|
|
&& ( Foo.prototype.foo2 instanceof Function )
|
|
|
|
),
|
|
|
|
"Abstract methods are copied into the new class prototype " +
|
|
|
|
"(empty base)"
|
|
|
|
);
|
|
|
|
},
|
2011-01-11 19:03:30 -05:00
|
|
|
|
|
|
|
|
2011-11-02 22:32:45 -04:00
|
|
|
'Can implement interface atop an exist class': function()
|
|
|
|
{
|
|
|
|
this.assertDoesNotThrow( function()
|
2011-01-11 19:03:30 -05:00
|
|
|
{
|
2011-11-02 22:32:45 -04:00
|
|
|
PlainFoo.implement( Type, Type2 );
|
|
|
|
}, Error, "Classes can implement interfaces" );
|
|
|
|
},
|
2011-01-11 19:03:30 -05:00
|
|
|
|
2011-01-04 00:37:54 -05:00
|
|
|
|
2011-11-02 22:32:45 -04:00
|
|
|
/**
|
|
|
|
* Ensure the same system mentioned above also applies to the extend()
|
|
|
|
* method on existing classes
|
|
|
|
*/
|
|
|
|
'Implementing interface atop existing class not usable by default':
|
|
|
|
function()
|
|
|
|
{
|
|
|
|
var result = PlainFoo.implement( Type );
|
2011-03-16 19:06:16 -04:00
|
|
|
|
2011-11-02 22:32:45 -04:00
|
|
|
this.assertEqual(
|
|
|
|
( Class.isClass( result ) ),
|
|
|
|
false,
|
|
|
|
"Result of implementing interfaces on an existing base is not " +
|
|
|
|
"usable as a Class"
|
|
|
|
);
|
|
|
|
},
|
2011-03-16 19:06:16 -04:00
|
|
|
|
2011-03-16 19:24:02 -04:00
|
|
|
|
2011-11-02 22:32:45 -04:00
|
|
|
'Abstract method copied into new class using existing base': function()
|
|
|
|
{
|
|
|
|
PlainFoo2 = AbstractClass.implement( Type, Type2 )
|
|
|
|
.extend( PlainFoo, {} );
|
|
|
|
|
|
|
|
this.assertOk(
|
|
|
|
( ( PlainFoo2.prototype.foo instanceof Function )
|
|
|
|
&& ( PlainFoo2.prototype.foo2 instanceof Function )
|
|
|
|
),
|
|
|
|
"Abstract methods are copied into the new class prototype " +
|
|
|
|
"(concrete base)"
|
|
|
|
);
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Since interfaces can contain only abstract methods, it stands to reason
|
|
|
|
* that any class implementing an interface without providing any concrete
|
|
|
|
* methods should be abstract by default.
|
|
|
|
*/
|
|
|
|
'Classes implementing interfaces are considered abstract by default':
|
|
|
|
function()
|
|
|
|
{
|
|
|
|
this.assertEqual(
|
|
|
|
( Foo.isAbstract() && PlainFoo2.isAbstract() ),
|
|
|
|
true,
|
|
|
|
"Classes that implements interface(s) are considered abstract if " +
|
|
|
|
"the implemented methods have no concrete implementations"
|
|
|
|
);
|
|
|
|
},
|
2011-03-16 19:24:02 -04:00
|
|
|
|
2011-03-16 19:50:47 -04:00
|
|
|
|
2011-11-02 22:32:45 -04:00
|
|
|
'Instances of classes are instances of their implemented interfaces':
|
|
|
|
function()
|
|
|
|
{
|
|
|
|
// concrete implementation so that we can instantiate it
|
|
|
|
var ConcreteFoo = Foo.extend(
|
|
|
|
{
|
|
|
|
'foo': function() {},
|
|
|
|
'foo2': function() {},
|
|
|
|
}),
|
|
|
|
|
|
|
|
concrete_inst = ConcreteFoo()
|
|
|
|
;
|
|
|
|
|
|
|
|
this.assertOk(
|
|
|
|
( concrete_inst.isInstanceOf( Type )
|
|
|
|
&& concrete_inst.isInstanceOf( Type2 )
|
|
|
|
),
|
|
|
|
"Instances of classes implementing interfaces are considered to " +
|
|
|
|
"be instances of the implemented interfaces"
|
|
|
|
);
|
|
|
|
|
|
|
|
this.assertEqual(
|
|
|
|
ConcreteFoo.isAbstract(),
|
|
|
|
false,
|
|
|
|
"Concrete implementations are not considered to be abstract"
|
|
|
|
);
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Consider the following scenario:
|
|
|
|
*
|
|
|
|
* MyClass.implement( Type ).extend( MyOtherClass, {} );
|
|
|
|
*
|
|
|
|
* What the above is essentially saying is: "I'd like to extend MyClass by
|
|
|
|
* implementing Type. Oh, no, wait, I'd actually like it to extend
|
|
|
|
* MyOtherClass." That doesn't make sense! Likely, it's unintended. Prevent
|
|
|
|
* confusion and bugs. Throw an error.
|
|
|
|
*/
|
|
|
|
'Cannot specify parent after implementing atop existing class': function()
|
2011-03-16 19:50:47 -04:00
|
|
|
{
|
2011-11-02 22:32:45 -04:00
|
|
|
this.assertThrows( function()
|
|
|
|
{
|
|
|
|
// should not be permitted
|
|
|
|
PlainFoo.implement( Type, Type2 ).extend( PlainFoo2, {} );
|
|
|
|
},
|
|
|
|
Error,
|
|
|
|
"Cannot specify new parent for extend() when implementing from " +
|
|
|
|
"existing class"
|
|
|
|
);
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Opposite of the above test. If a parent wasn't specified to begin with,
|
|
|
|
* then we're fine to specify it in extend().
|
|
|
|
*/
|
|
|
|
'Can specify parent if implementing atop empty class': function()
|
|
|
|
{
|
|
|
|
this.assertDoesNotThrow(
|
|
|
|
function()
|
|
|
|
{
|
|
|
|
// this /should/ work
|
|
|
|
AbstractClass.implement( Type ).extend( PlainFoo, {} );
|
|
|
|
},
|
|
|
|
Error,
|
|
|
|
"Can specify parent for exetnd() when implementing atop an " +
|
|
|
|
"empty base"
|
|
|
|
);
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* If more than two arguments are given to extend(), then the developer
|
|
|
|
* likely does not understand the API. Throw an error to prevent some
|
|
|
|
* bugs/confusion.
|
|
|
|
*/
|
|
|
|
'Throws exception if extend contains too many arguments': function()
|
|
|
|
{
|
|
|
|
this.assertThrows( function()
|
|
|
|
{
|
|
|
|
Class.implement( Type ).extend( PlainFoo, {}, 'extra' );
|
|
|
|
}, Error, "extend() after implementing accepts no more than two args" );
|
|
|
|
},
|
|
|
|
} );
|
2011-03-16 19:50:47 -04:00
|
|
|
|