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
|
* synchronous; it is also undefined whether LISTENER will receive
|
||||||
* messages emitted by event EV before having hooked the event.
|
* 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.
|
* An implementation shall not automatically unhook LISTENER (i.e.
|
||||||
* without use of #removeListener) from event EV unless the
|
* 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