diff --git a/lib/Trait.js b/lib/Trait.js index 0ec841e..e92a973 100644 --- a/lib/Trait.js +++ b/lib/Trait.js @@ -36,17 +36,22 @@ function Trait() { switch ( arguments.length ) { + case 0: + throw Error( "Missing trait name or definition" ); + case 1: - return Trait.extend.apply( this, arguments ); - break; + return ( typeof arguments[ 0 ] === 'string' ) + ? _createStaging.apply( this, arguments ) + : Trait.extend.apply( this, arguments ); case 2: return createNamedTrait.apply( this, arguments ); - break; - - default: - throw Error( "Missing trait name or definition" ); } + + throw Error( + "Expecting at most two arguments for definition of named " + + "Trait " + name + "'; " + arguments.length + " given" + ); }; @@ -60,14 +65,6 @@ function Trait() */ function createNamedTrait( name, dfn ) { - if ( arguments.length > 2 ) - { - throw Error( - "Expecting at most two arguments for definition of named " + - "Trait " + name + "'; " + arguments.length + " given" - ); - } - if ( typeof name !== 'string' ) { throw Error( @@ -76,11 +73,26 @@ function createNamedTrait( name, dfn ) } dfn.__name = name; - return Trait.extend( dfn ); } +function _createStaging( name ) +{ + return { + extend: function( dfn ) + { + return createNamedTrait( name, dfn ); + }, + + implement: function() + { + return createImplement( arguments, name ); + }, + }; +} + + Trait.extend = function( dfn ) { // we may have been passed some additional metadata @@ -244,19 +256,37 @@ function _parseGetSet( name, value, keywords, h ) */ Trait.implement = function() { - var ifaces = arguments; + return createImplement( arguments ); +}; + +/** + * Create a staging object from which a trait implementing a set of + * interfaces may be defined + * + * @param {...Function} interfaces interfaces to implement + * @param {string=} name optional trait name + * + * @return {Object} staged trait object + */ +function createImplement( ifaces, name ) +{ return { - extend: function() + extend: function( dfn ) { + if ( name ) + { + dfn.__name = name; + } + // pass our interface metadata as the invocation context - return Trait.extend.apply( + return Trait.extend.call( { __$$meta: { ifaces: ifaces } }, - arguments + dfn ); }, }; -}; +} /** diff --git a/test/Trait/NamedTest.js b/test/Trait/NamedTest.js index c70de99..68cbad6 100644 --- a/test/Trait/NamedTest.js +++ b/test/Trait/NamedTest.js @@ -1,7 +1,7 @@ /** * Tests named trait definitions * - * Copyright (C) 2014 Free Software Foundation, Inc. + * Copyright (C) 2014 Mike Gerwitz * * This file is part of GNU ease.js. * @@ -23,8 +23,9 @@ require( 'common' ).testCase( { caseSetUp: function() { - this.Sut = this.require( 'Trait' ); - this.Class = this.require( 'class' ); + this.Sut = this.require( 'Trait' ); + this.Class = this.require( 'class' ); + this.Interface = this.require( 'interface' ); }, @@ -84,4 +85,86 @@ require( 'common' ).testCase( Sut( {}, {} ); } ); }, + + + /** + * Just as is the case with classes, providing only a name for the trait + * should create a staging object with which subsequent calls may be + * chained, just as if those calls were made on Trait directly. The + * difference is that the name shall propagate. + */ + 'Providing only trait name creates staging object': function() + { + var Sut = this.Sut; + this.assertDoesNotThrow( function() + { + // this does not create a trait, but it should be acceptable + // just as Class( "Foo" ) is + Sut( "Foo" ); + } ); + }, + + + /** + * The named trait staging object should permit direct extension using + * an extend method, which should do the same thing as Trait.extend. + */ + 'Can extend named trait staging object': function() + { + var Sut = this.Sut, + expected = {}, + name = "Foo", + T = null; + + this.assertDoesNotThrow( function() + { + // this does not create a trait, but it should be acceptable + // just as Class( "Foo" ) is + T = Sut( name ) + .extend( { foo: function() { return expected; } } ); + } ); + + // ensure that extending worked as expected + this.assertStrictEqual( + this.Class( {} ).use( T )().foo(), + expected + ); + + // ensure that trait was properly named + this.assertOk( T.toString().match( name ) ); + }, + + + /** + * The implement method on the named staging object should work just as + * Trait.implement. + */ + 'Can implement interface using named trait staging object': + function() + { + + var Sut = this.Sut, + expected = {}, + name = "Foo", + I = this.Interface( {} ), + I2 = this.Interface( {} ), + T = null; + + this.assertDoesNotThrow( function() + { + // this does not create a trait, but it should be acceptable + // just as Class( "Foo" ) is + T = Sut( "Foo" ) + .implement( I, I2 ) + .extend( {} ); + } ); + + // ensure that implement worked as intended + var inst = this.Class( {} ).use( T )(); + this.assertOk( this.Class.isA( I, inst ) ); + this.assertOk( this.Class.isA( I2, inst ) ); + + // ensure that trait was properly named + this.assertOk( T.toString().match( name ) ); + }, } );