From 455d3a58151b5e058919b0017ae27653db173f21 Mon Sep 17 00:00:00 2001 From: Mike Gerwitz Date: Mon, 10 Feb 2014 23:14:05 -0500 Subject: [PATCH] Added immediate partial class invocation support after mixin --- lib/class.js | 55 +++++++++--------- test/Trait/ImmediateTest.js | 108 ++++++++++++++++++++++++++++++++++++ 2 files changed, 138 insertions(+), 25 deletions(-) create mode 100644 test/Trait/ImmediateTest.js diff --git a/lib/class.js b/lib/class.js index d74608a..2f8ad81 100644 --- a/lib/class.js +++ b/lib/class.js @@ -371,32 +371,37 @@ function createImplement( base, ifaces, cname ) function createUse( base, traits ) { - return { - extend: function() - { - var args = Array.prototype.slice.call( arguments ), - dfn = args.pop(), - ext_base = args.pop(); - - // "mix" each trait into the provided definition object - for ( var i = 0, n = traits.length; i < n; i++ ) - { - traits[ i ].__mixin( dfn ); - } - - var C = extend.call( null, ( base || ext_base ), dfn ), - meta = ClassBuilder.getMeta( C ); - - // add each trait to the list of implemented types so that the - // class is considered to be of type T in traits - for ( var i = 0, n = traits.length; i < n; i++ ) - { - meta.implemented.push( traits[ i ] ); - } - - return C; - }, + var partial = function() + { + return partial.extend( {} ).apply( null, arguments ); }; + + partial.extend = function() + { + var args = Array.prototype.slice.call( arguments ), + dfn = args.pop(), + ext_base = args.pop(); + + // "mix" each trait into the provided definition object + for ( var i = 0, n = traits.length; i < n; i++ ) + { + traits[ i ].__mixin( dfn ); + } + + var C = extend.call( null, ( base || ext_base ), dfn ), + meta = ClassBuilder.getMeta( C ); + + // add each trait to the list of implemented types so that the + // class is considered to be of type T in traits + for ( var i = 0, n = traits.length; i < n; i++ ) + { + meta.implemented.push( traits[ i ] ); + } + + return C; + }; + + return partial; } diff --git a/test/Trait/ImmediateTest.js b/test/Trait/ImmediateTest.js new file mode 100644 index 0000000..5aaa595 --- /dev/null +++ b/test/Trait/ImmediateTest.js @@ -0,0 +1,108 @@ +/** + * Tests immediate definition/instantiation + * + * Copyright (C) 2014 Mike Gerwitz + * + * 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 . + */ + +require( 'common' ).testCase( +{ + caseSetUp: function() + { + this.Sut = this.require( 'Trait' ); + this.Class = this.require( 'class' ); + }, + + + /** + * In our most simple case, mixing a trait into an empty base class and + * immediately invoking the resulting partial class (without explicitly + * extending) should have the effect of instantiating a concrete version + * of the trait (so long as that is permitted). While this test exists + * to ensure consistency throughout the system, it may be helpful in + * situations where a trait is useful on its own. + */ + 'Invoking partial class after mixin instantiates': function() + { + var called = false; + + var T = this.Sut( + { + 'public foo': function() + { + called = true; + }, + } ); + + // mixes T into an empty base class and instantiates + this.Class.use( T )().foo(); + this.assertOk( called ); + }, + + + /** + * This is the most useful and conventional form of mixin---runtime, + * atop of an existing class. In this case, we provide a short-hand form + * of instantiation to avoid the ugly pattern of `.extend( {} )()'. + */ + 'Can invoke partial mixin atop of non-empty base': function() + { + var called_foo = false, + called_bar = false; + + var C = this.Class( + { + 'public foo': function() { called_foo = true; }, + } ); + + var T = this.Sut( + { + 'public bar': function() { called_bar = true; }, + } ); + + // we must ensure not only that we have mixed in the trait, but that + // we have also maintained C's interface + var inst = C.use( T )(); + inst.foo(); + inst.bar(); + + this.assertOk( called_foo ); + this.assertOk( called_bar ); + }, + + + /** + * Ensure that the partial invocation shorthand is equivalent to the + * aforementioned `.extend( {} ).apply( null, arguments )'. + */ + 'Partial arguments are passed to class constructor': function() + { + var given = null, + expected = { foo: 'bar' }; + + var C = this.Class( + { + __construct: function() { given = arguments; }, + } ); + + var T = this.Sut( {} ); + + C.use( T )( expected ); + this.assertStrictEqual( given[ 0 ], expected ); + }, +} ); +