1
0
Fork 0
easejs/test/Trait/ClassVirtualTest.js

134 lines
5.2 KiB
JavaScript

/**
* Tests overriding virtual class methods using mixins
*
* Copyright (C) 2014 Free Software Foundation, Inc.
*
* This file is part of GNU ease.js.
*
* ease.js is free software: you can redistribute it and/or modify
* it under the 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.
*
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* These tests vary from those in VirtualTest in that, rather than a class
* overriding a virtual method defined within a trait, a trait is overriding
* a method in the class that it is mixed into. In particular, since
* overrides require that the super method actually exist, this means that a
* trait must implement or extend a common interface.
*
* It is this very important (and powerful) system that allows traits to be
* used as stackable modifications, similar to how one would use the
* decorator pattern (but more tightly coupled).
*
* See also ClassExtendTest, which is related in that it can too define
* virtual methods.
*/
require( 'common' ).testCase(
{
caseSetUp: function()
{
this.Sut = this.require( 'Trait' );
this.Class = this.require( 'class' );
this.AbstractClass = this.require( 'class_abstract' );
this.Interface = this.require( 'interface' );
},
/**
* This is a concept borrowed from Scala: consider class C and trait T,
* both implementing interface I which declares method M. T should be
* able to override C.M so long as it is concrete, but to do so, we need
* some way of telling ease.js that we are overriding at time of mixin;
* otherwise, override does not make sense, because I.M is clearly
* abstract and there is nothing to override.
*/
'Mixin can override virtual concrete method defined by interface':
function()
{
var called = false,
I = this.Interface( { foo: [] } );
var T = this.Sut.implement( I ).extend(
{
// the keyword combination `abstract override' indicates that we
// should override whatever concrete implementation was defined
// before our having been mixed in
'abstract override foo': function()
{
called = true;
},
} );
var _self = this;
var C = this.Class.implement( I ).extend(
{
// this should be overridden by the mixin and should therefore
// never be called (for __super tests, see LinearizationTest)
'virtual foo': function()
{
_self.fail( false, true,
"Concrete class method was not overridden by mixin"
);
},
} );
// mixing in a trait atop of C should yield the results described
// above due to the `abstract override' keyword combination
C.use( T )().foo();
this.assertOk( called );
},
/**
* Virtual methods for traits are handled via a series of proxy methods
* that determine, at runtime (as opposed to when the class is created),
* where the call should go. (At least that was the implementation at
* the time this test was written.) This test relies on the proper
* parameter metadata being set on those proxy methods so that the
* necessary length requirements can be validated.
*
* This was a bug in the initial implemenation: the above tests did not
* catch it because the virtual methods had no arguments. The initial
* problem was that, since __length was not defined on the generated
* method that was recognized as the override, it was always zero, which
* always failed if there were any arguments on the virtual method. The
* reverse case was also a problem, but it didn't manifest as an
* error---rather, it did *not* error when it should have.
*
* Note the instantiation in these cases: this is because the trait
* implementation lazily performs the mixin on first use.
*/
'Subtype must meet compatibility requirements of virtual trait method':
function()
{
var _self = this;
var C = this.Class.use(
this.Sut( { 'virtual foo': function( a, b ) {} } )
);
this.assertThrows( function()
{
// does not meet param requirements (note the
// instantiation---traits defer processing until they are used)
C.extend( { 'override foo': function( a ) {} } )();
} );
this.assertDoesNotThrow( function()
{
// does not meet param requirements (note the
// instantiation---traits defer processing until they are used)
C.extend( { 'override foo': function( a, b ) {} } )();
} );
},
} );