358 lines
9.7 KiB
JavaScript
358 lines
9.7 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( ids )
|
|
{
|
|
return this.defineEvents( ids );
|
|
},
|
|
|
|
// emit is protected
|
|
evEmit( ev )
|
|
{
|
|
return this.emit.apply( this, arguments );
|
|
}
|
|
} );
|
|
|
|
|
|
describe( 'event.Evented', () =>
|
|
{
|
|
var stub;
|
|
|
|
beforeEach( () =>
|
|
{
|
|
stub = EvStub();
|
|
} );
|
|
|
|
|
|
describe( '#defineEvents', () =>
|
|
{
|
|
/**
|
|
* 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', () =>
|
|
{
|
|
expect( () => stub.evDefineEvents( 'string' ) )
|
|
.to.throw( TypeError, "array" );
|
|
} );
|
|
|
|
|
|
it( 'accepts array argument', () =>
|
|
{
|
|
expect( () => stub.evDefineEvents( [] ) )
|
|
.to.not.throw( TypeError );
|
|
} );
|
|
|
|
|
|
it( 'returns self', () =>
|
|
{
|
|
expect( stub.evDefineEvents( [] ) ).to.equal( stub );
|
|
} );
|
|
|
|
|
|
/**
|
|
* This relies on functionality proved below
|
|
*/
|
|
it( 'defines each event in provided list', () =>
|
|
{
|
|
var events = [ 'a', 'b' ];
|
|
stub.evDefineEvents( events );
|
|
|
|
var i = events.length;
|
|
while ( i-- ) {
|
|
// will fail if event did not register
|
|
expect( () => stub.on( events[ i ], ()=>{} ) )
|
|
.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', () =>
|
|
{
|
|
var evs = [ 'a', 'b' ];
|
|
|
|
// define each separately
|
|
for ( var i = 0; i < evs.length; i++ ) {
|
|
stub.evDefineEvents( [ evs[ i ] ] );
|
|
|
|
// will fail if event did not register
|
|
expect( () => stub.on( evs[ i ], ()=>{} ) )
|
|
.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', () =>
|
|
{
|
|
it( 'in separate calls', () =>
|
|
{
|
|
var name = 'ev';
|
|
stub.evDefineEvents( [ name ] );
|
|
|
|
expect( () => stub.evDefineEvents( [ name ] ) )
|
|
.to.throw( Error, name );
|
|
} );
|
|
|
|
|
|
it( 'in a single call', () =>
|
|
{
|
|
var dup = 'foo';
|
|
|
|
expect( () => stub.evDefineEvents( [ dup, 'a', dup ] ) )
|
|
.to.throw( Error, dup );
|
|
} );
|
|
} );
|
|
} );
|
|
|
|
|
|
describe( '#on', () =>
|
|
{
|
|
it( 'does not allow hooking undeclared events', () =>
|
|
{
|
|
var badevent = 'bazbar';
|
|
|
|
expect( () => stub.on( badevent, ()=>{} ) )
|
|
.to.throw( Error, badevent );
|
|
} );
|
|
|
|
|
|
it( 'allows hooking declared events', () =>
|
|
{
|
|
var name = 'testev';
|
|
stub.evDefineEvents( [ name ] )
|
|
.on( name, ()=>{} );
|
|
} );
|
|
|
|
|
|
it( 'returns self', () =>
|
|
{
|
|
var name = 'testev';
|
|
|
|
var ret = stub.evDefineEvents( [ name ] )
|
|
.on( name, ()=>{} );
|
|
|
|
expect( ret ).to.equal( stub );
|
|
} );
|
|
|
|
|
|
it( 'requires that callback is a function', () =>
|
|
{
|
|
var ev = 'foo';
|
|
stub.evDefineEvents( [ ev ] );
|
|
|
|
// OK
|
|
expect( () => stub.on( ev, ()=>{} ) )
|
|
.to.not.throw( TypeError );
|
|
|
|
// bad
|
|
expect( () => stub.on( ev, "kittens" ) )
|
|
.to.throw( TypeError );
|
|
} );
|
|
} );
|
|
|
|
|
|
describe( '#emit', () =>
|
|
{
|
|
it( 'cannot emit undefined events', () =>
|
|
{
|
|
var ev = 'unknown';
|
|
|
|
expect( () => stub.evEmit( ev ) )
|
|
.to.throw( Error, ev );
|
|
} );
|
|
|
|
|
|
it( 'invokes all callbacks attached with #on', () =>
|
|
{
|
|
var ev = 'foo',
|
|
called = 0;
|
|
|
|
stub.evDefineEvents( [ ev ] )
|
|
.on( ev, () => called++ );
|
|
|
|
expect( () => 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', ( done ) =>
|
|
{
|
|
var ev = 'foo',
|
|
a = {},
|
|
b = [];
|
|
|
|
stub.evDefineEvents( [ ev ] )
|
|
.on( ev, ( 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', () =>
|
|
{
|
|
var ev = 'foo',
|
|
called = [],
|
|
n = 2;
|
|
|
|
stub.evDefineEvents( [ ev ] );
|
|
|
|
// add N callbacks for EV
|
|
for ( let i = 0; i < n; i++ ) {
|
|
stub.on( ev, () => called[ i ] = true );
|
|
}
|
|
|
|
// 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', () =>
|
|
{
|
|
var ev = 'foo',
|
|
wrongev = 'bar';
|
|
|
|
stub.evDefineEvents( [ ev, wrongev ] );
|
|
|
|
var called = false;
|
|
stub.on( ev, () => called = true );
|
|
stub.on( wrongev, () => { 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', () =>
|
|
{
|
|
var ev = 'foo';
|
|
expect( () => stub.evDefineEvents( [ ev ] ).evEmit( ev ) )
|
|
.to.not.throw( Error );
|
|
} );
|
|
|
|
|
|
it( 'returns self', () =>
|
|
{
|
|
var ev = 'foo';
|
|
expect( stub.evDefineEvents( [ ev ] ).evEmit( ev ) )
|
|
.to.equal( stub );
|
|
} );
|
|
} );
|
|
|
|
|
|
/**
|
|
* This is important to support stackable traits for custom schedulers.
|
|
*/
|
|
describe( '#scheduleCallbacks', () =>
|
|
{
|
|
var ev = 'foo',
|
|
a = 'bar',
|
|
b = 'baz';
|
|
|
|
it( 'can be overridden by subtypes', ( done ) =>
|
|
{
|
|
common.checkOverride( EvStub, 'scheduleCallbacks',
|
|
( 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', ( done ) =>
|
|
{
|
|
var a = function() {},
|
|
b = function() {};
|
|
|
|
common.checkOverride( EvStub, 'scheduleCallbacks',
|
|
( _, evc, __ ) =>
|
|
{
|
|
expect( evc ).to.include( a );
|
|
expect( evc ).to.include( b );
|
|
done();
|
|
}
|
|
)()
|
|
.evDefineEvents( [ ev ] )
|
|
.on( ev, a )
|
|
.on( ev, b )
|
|
.evEmit( ev );
|
|
} );
|
|
} );
|
|
} );
|
|
|