/** * 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 . * * 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.)} 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 ); } ); } ); }