jstonic/test/event/EventedTest.js

392 lines
11 KiB
JavaScript

/**
* Tests trait providing 1-N message dispatch
*
* Copyright (C) 2014 Mike Gerwitz
*
* This file is part of jsTonic.
*
* jstonic 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/>.
*/
var Sut = require( '../../' ).event.Evented,
Class = require( 'easejs' ).Class,
expect = require( 'chai' ).expect,
common = require( '../lib' ),
EvStub = Class.use( Sut ).extend(
{
// defineEvents is protected
evDefineEvents: function( ids )
{
return this.defineEvents( ids );
},
// emit is protected
evEmit: function( ev )
{
return this.emit.apply( this, arguments );
}
} );
describe( 'event.Evented', function()
{
var stub;
beforeEach( function()
{
stub = EvStub();
} );
describe( '#defineEvents', function()
{
/**
* We aren't going to bother testing every possibly value; let's
* just see if the most common case (probably) is caught.
*/
it( 'rejects non-array argument', function()
{
expect( function()
{
stub.evDefineEvents( 'string' );
} ).to.throw( TypeError, "array" );
} );
it( 'accepts array argument', function()
{
expect( function()
{
stub.evDefineEvents( [] );
} ).to.not.throw( TypeError );
} );
it( 'returns self', function()
{
expect( stub.evDefineEvents( [] ) ).to.equal( stub );
} );
/**
* This relies on functionality proven below
*/
it( 'defines each event in provided list', function()
{
var events = [ 'a', 'b' ];
stub.evDefineEvents( events );
var i = events.length;
while ( i-- ) {
expect( function()
{
// will fail if event did not register
stub.on( events[ i ], function() {} );
} ).to.not.throw( Error );
}
} );
/**
* While at first glance this may seem like an odd thing to want to
* do, this will come in useful when exposed via an API that defines
* events individually.
*/
it( 'merges definitions in multiple calls', function()
{
var evs = [ 'a', 'b' ];
// define each separately
for ( var i = 0; i < evs.length; i++ ) {
stub.evDefineEvents( [ evs[ i ] ] );
expect( function()
{
// will fail if event did not register
stub.on( evs[ i ], function() {} );
} ).to.not.throw( Error );
}
} );
/**
* We do not want someone to declare an event thinking that the id
* is free, which risks mixing handlers from what is supposed to be
* two separate events.
*/
describe( 'throws exception on duplicate events', function()
{
it( 'in separate calls', function()
{
var name = 'ev';
stub.evDefineEvents( [ name ] );
expect( function()
{
stub.evDefineEvents( [ name ] );
} ).to.throw( Error, name );
} );
it( 'in a single call', function()
{
var dup = 'foo';
expect( function()
{
stub.evDefineEvents( [ 'a', dup, 'b', dup ] );
} ).to.throw( Error, dup );
} );
} );
} );
describe( '#on', function()
{
it( 'does not allow hooking undeclared events', function()
{
var badevent = 'bazbar';
expect( function()
{
stub.on( badevent, function() {} );
} ).to.throw( Error, badevent );
} );
it( 'allows hooking declared events', function()
{
var name = 'testev';
stub.evDefineEvents( [ name ] )
.on( name, function() {} );
} );
it( 'returns self', function()
{
var name = 'testev';
var ret = stub.evDefineEvents( [ name ] )
.on( name, function() {} );
expect( ret ).to.equal( stub );
} );
it( 'requires that callback is a function', function()
{
var ev = 'foo';
stub.evDefineEvents( [ ev ] );
// OK
expect( function()
{
stub.on( ev, function() {} );
} ).to.not.throw( TypeError );
// bad
expect( function()
{
stub.on( ev, "kittens" );
} ).to.throw( TypeError );
} );
} );
describe( '#emit', function()
{
it( 'cannot emit undefined events', function()
{
var ev = 'unknown';
expect( function()
{
stub.evEmit( ev );
} ).to.throw( Error, ev );
} );
it( 'invokes all callbacks attached with #on', function()
{
var ev = 'foo',
called = 0;
stub.evDefineEvents( [ ev ] )
.on( ev, function()
{
called++;
} );
expect( function()
{
stub.evEmit( ev );
} ).to.not.throw( Error );
// make sure the callback was invoked only once
expect( called ).to.equal( 1 );
} );
/**
* It is courteous for event handler to *not* modify the values they
* are given. Otherwise, the emitter must take care to clone or
* encapsulate the values if this is a concern.
*/
it( 'passes all emit args by reference to callbacks', function( done )
{
var ev = 'foo',
a = {},
b = [];
stub.evDefineEvents( [ ev ] )
.on( ev, function( givena, givenb )
{
expect( givena ).to.equal( a );
expect( givenb ).to.equal( b );
done();
} )
.evEmit( ev, a, b );
} );
/**
* The default event scheduler synchronously invokes all callbacks
* in an undefined order (that is: the order may change in the
* future). This is not desirable for all cases, but that's not the
* point here.
*/
it( 'invokes multiple callbacks synchronously', function()
{
var ev = 'foo',
called = [],
n = 2;
stub.evDefineEvents( [ ev ] );
// add N callbacks for EV
for ( var i = 0; i < n; i++ ) {
( function( i )
{
stub.on( ev, function()
{
called[ i ] = true;
} );
} )( i );
}
// trigger callbacks
stub.evEmit( ev );
// by now, we care about two things: that all callbacks were
// invoked and that they have been invoked by this point,
// meaning that they were done synchronously
while ( n-- ) {
expect( called[ n ] ).to.equal( true );
}
} );
/**
* When we emit an event, only the callbacks for that specific event
* should be invoked.
*/
it( 'does not mix callbacks from other events', function()
{
var ev = 'foo',
wrongev = 'bar';
stub.evDefineEvents( [ ev, wrongev ] );
var called = false;
stub.on( ev, function() { called = true; } );
stub.on( wrongev, function() { throw Error( "Thar be demons" ); } );
// wrongev callback should *not* be called
stub.evEmit( ev );
expect( called ).to.equal( true );
} );
/**
* It is common to leave events unhooked.
*/
it( 'does not fail when emitting unhooked events', function()
{
var ev = 'foo';
expect( function()
{
// no hooks, but emit
stub.evDefineEvents( [ ev ] ).evEmit( ev );
} ).to.not.throw( Error );
} );
it( 'returns self', function()
{
var ev = 'foo';
expect( stub.evDefineEvents( [ ev ] ).evEmit( ev ) )
.to.equal( stub );
} );
} );
/**
* This is important to support stackable traits for custom schedulers.
*/
describe( '#scheduleCallbacks', function()
{
var ev = 'foo',
a = 'bar',
b = 'baz';
it( 'can be overridden by subtypes', function( done )
{
common.checkOverride( EvStub, 'scheduleCallbacks',
function( given_ev, _, given_args )
{
expect( given_ev ).to.equal( ev );
expect( given_args ).to.deep.equal( [ a, b ] );
done();
}
)().evDefineEvents( [ ev ] ).evEmit( ev, a, b );
} );
/**
* The listeners are encapsulated within the trait, so they must be
* passed to us for processing.
*/
it( 'is provided with callbacks to invoke', function( done )
{
var a = function() {},
b = function() {};
common.checkOverride( EvStub, 'scheduleCallbacks',
function( _, evc, __ )
{
expect( evc ).to.include( a );
expect( evc ).to.include( b );
done();
}
)()
.evDefineEvents( [ ev ] )
.on( ev, a )
.on( ev, b )
.evEmit( ev );
} );
} );
} );