Initial Eventable conformance test case (#on/#addListener)
These conformance test cases will be an excellent disciplinary tool, ensuring that any implementation of Eventable conforms strictly to its specification; this will provide developers guarantees that the implementation will work as expected polymorphically.events
parent
a2deba59ad
commit
aab86a47cd
|
@ -74,7 +74,10 @@ module.exports = Interface( 'Eventable',
|
|||
* synchronous; it is also undefined whether LISTENER will receive
|
||||
* messages emitted by event EV before having hooked the event.
|
||||
*
|
||||
* The validity of the event identifier EV is undefined.
|
||||
* The validity of the event identifier EV is undefined, except that it
|
||||
* must be a string and an empty string shall throw a TypeError (as this
|
||||
* may very well indicate a bug in the calling code). LISTENER shall not
|
||||
* be required to declare any number of parameters.
|
||||
*
|
||||
* An implementation shall not automatically unhook LISTENER (i.e.
|
||||
* without use of #removeListener) from event EV unless the
|
||||
|
|
|
@ -0,0 +1,162 @@
|
|||
/**
|
||||
* Tests for conforming Eventable implementation
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
* A conforming implementation must pass each of these tests. See the
|
||||
* Eventable interface itself for additional requirements and documentation.
|
||||
*
|
||||
* To use these tests, invoke the exported function with a constructor
|
||||
* function returning an instance of the SUT.
|
||||
*/
|
||||
|
||||
let expect = require( 'chai' ).expect,
|
||||
types = require( '../../' ).util.types;
|
||||
|
||||
let _fvoid = () => {},
|
||||
_ev = 'ev',
|
||||
|
||||
_nonstr = types.typevals.nonstr,
|
||||
_nonf = types.typevals.nonfunc;
|
||||
|
||||
// data passed to ctor to prepare for test
|
||||
let _events = [ _ev ];
|
||||
|
||||
|
||||
/**
|
||||
* Invoke conformance tests
|
||||
*
|
||||
* CTOR will be passed an array of events that the conformance tests will
|
||||
* make use of, allowing them to be registered if needed (for validation
|
||||
* assertions).
|
||||
*
|
||||
* This test case will ideally be invoked after the top-level `describe` of
|
||||
* the SUT test case so that the output reads as "SUT, conforming to
|
||||
* Eventable, ...".
|
||||
*
|
||||
* @param {function(Array.<string>)} ctor SUT constructor function
|
||||
*
|
||||
* @return {undefined}
|
||||
*/
|
||||
module.exports = ctor =>
|
||||
{
|
||||
// shorthand for SUT construction
|
||||
let meta_ctor = () => ctor( _events );
|
||||
|
||||
/**
|
||||
* #on and #addListener are one and the same, but detecting that they
|
||||
* alias each other is not reliable for various reasons; instead, we'll
|
||||
* run the tests on both methods.
|
||||
*/
|
||||
describe( 'conforming to Eventable', () =>
|
||||
{
|
||||
[ 'on', 'addListener' ].forEach( x =>
|
||||
_onTests( meta_ctor, x ) );
|
||||
} );
|
||||
};
|
||||
|
||||
|
||||
function _onTests( ctor, on )
|
||||
{
|
||||
/**
|
||||
* Note that by using _fvoid, we are implicitly testing the requirement
|
||||
* that the listener shall not be required to declare any number of
|
||||
* parameters (because we defined no parameters).
|
||||
*/
|
||||
describe( `#${on}`, () =>
|
||||
{
|
||||
/**
|
||||
* We must test both inputs at once since we cannot otherwise say
|
||||
* with confidence which parameter is non-conforming.
|
||||
*/
|
||||
it( 'accepts string event id with listener function', () =>
|
||||
{
|
||||
expect( () =>
|
||||
{
|
||||
ctor()[ on ]( _ev, _fvoid );
|
||||
} ).to.not.throw( Error );
|
||||
} );
|
||||
|
||||
|
||||
/**
|
||||
* A TypeError should be thrown when the event id is empty. The
|
||||
* rationale behind this is that the event id may be dynamically
|
||||
* determined at runtime, or may otherwise be stored in a variable,
|
||||
* which probably is not supposed to be empty; if it is, this could
|
||||
* represent a logic error (such as an incomplete conditional or
|
||||
* table lookup failure). More likely, this would result in
|
||||
* `undefined`, which is covered in below tests.
|
||||
*
|
||||
* Implementations wishing to use an empty string to indicate "all
|
||||
* events" could instead use, for example, `*` (motivated by shell
|
||||
* globbing).
|
||||
*/
|
||||
it( 'does not accept an empty event id', () =>
|
||||
{
|
||||
expect( () => ctor()[ on ]( '', _fvoid ) )
|
||||
.to.throw( TypeError );
|
||||
} );
|
||||
|
||||
|
||||
/**
|
||||
* All event identifiers should be strings; this provides
|
||||
* consistency between all implementations and helps to weed out
|
||||
* runtime bugs (see the "empty event id" test for examples).
|
||||
*/
|
||||
it( 'does not accept non-string event ids', () =>
|
||||
{
|
||||
_nonstr.forEach( badev =>
|
||||
{
|
||||
expect( () => ctor()[ on ]( badev, _fvoid ) )
|
||||
.to.throw( TypeError );
|
||||
} );
|
||||
} );
|
||||
|
||||
|
||||
/**
|
||||
* Same rationale as the event string argument.
|
||||
*/
|
||||
it( 'does not accept non-function listeners', () =>
|
||||
{
|
||||
_nonf.forEach( badf =>
|
||||
{
|
||||
expect( () => ctor()[ on ]( _ev, badf ) )
|
||||
.to.throw( TypeError );
|
||||
} );
|
||||
} );
|
||||
|
||||
|
||||
/**
|
||||
* When hooking multiple events on a single object, it is convenient
|
||||
* to be able to do so concisely without having to repeat the name
|
||||
* of the object reference.
|
||||
*
|
||||
* Since exceptions are used to indicate error conditions, there is
|
||||
* also no other useful value to return that would not break
|
||||
* encapsulation.
|
||||
*/
|
||||
it( 'returns self for method chaining', () =>
|
||||
{
|
||||
let inst = ctor();
|
||||
|
||||
expect( inst[ on ]( _ev, _fvoid ) )
|
||||
.to.equal( inst );
|
||||
} );
|
||||
} );
|
||||
}
|
||||
|
Loading…
Reference in New Issue