SOURCE CODE: Uize.Test.Uize.Class.mChildObjectEventBindings (view docs)

/*______________
|       ______  |   U I Z E    J A V A S C R I P T    F R A M E W O R K
|     /      /  |   ---------------------------------------------------
|    /    O /   |    MODULE : Uize.Test.Uize.Class.mChildObjectEventBindings Class
|   /    / /    |
|  /    / /  /| |    ONLINE : http://www.uize.com
| /____/ /__/_| | COPYRIGHT : (c)2014-2016 UIZE
|          /___ |   LICENSE : Available under MIT License or GNU General Public License
|_______________|             http://www.uize.com/license.html
*/

/* Module Meta Data
  type: Test
  importance: 3
  codeCompleteness: 100
  docCompleteness: 100
*/

/*?
  Introduction
    The =Uize.Test.Uize.Class.mChildObjectEventBindings= module defines a suite of unit tests for the =Uize.Class.mChildObjectEventBindings= mixin module.

    *DEVELOPERS:* `Ben Ilegbodu`, original code contributed by `Zazzle Inc.`
*/

Uize.module ({
  name:'Uize.Test.Uize.Class.mChildObjectEventBindings',
  superclass:'Uize.Test.Class',
  required:[
    'Uize.Comm',
    'Uize.Test.Widget',
    'Uize.Widget'
  ],
  builder:function (_superclass) {
    'use strict';

    var
      _Uize = Uize,

      _eventBindingsFunctionName = 'eventBindings',

      _global = Uize.global(),
      _processArrayAsync = Uize.Comm.processArrayAsync,
      _defaultHandler = function(_event, _source) { _event.handler.call(this, _event, _source) },
      _defaultChangedHandler = function(_event, _source) { this.get(_event.name.slice(8)).call(this, _event, _source) },
      _originalWindow,

      _class = _superclass.subclass({
        moduleToTest:'Uize.Class.mChildObjectEventBindings'
      })
    ;

    function _getMockDomNode(_name) { return Uize.Test.Widget.getMockDomNode(_name) }

    function _getTestWidgetClass(_eventBindings, _children) {
      return Uize.Widget.subclass (
        _Uize.copyInto(
          {
            mixins:Uize.Class.mChildObjectEventBindings,
            childObjectEventBindings:{
              declaration:_eventBindingsFunctionName,
              instanceProperty:'children',
              addedInstanceProperty:'addedChildren',
              additionalTypes:{
                dom:{
                  namePrefix:'#',
                  wireWhenever:'wired',
                  getObjectMethod:'getNode',
                  wireObjectMethod:'wireNode',
                  defaultFireIf:'enabledInherited,!busyInherited'
                }
              }
            },
            omegastructor:function() {
              this.addChildren(Uize.lookup(_children, {widgetClass:Uize.Widget}));
            }
          },
          _Uize.pairUp(_eventBindingsFunctionName, _eventBindings)
        )
      );
    }

    function _getTestWidgetClassInstance(_eventBindings, _children, _nodes, _instanceProperties) {
      return _getTestWidgetClass(_eventBindings, _children)(
        Uize.copyInto(
          {
            nodeMap:Uize.map(
              Uize.lookup(_nodes),
              function(_value, _node) { return _getMockDomNode(_node) }
            )
          },
          _instanceProperties
        )
      );
    }

    function _generateTest(_title, _eventBindingsShorthand, _eventBindingsVerbose, _wiredEvents, _deferredChildren) {
      function _generateSyntaxTests(_isVerbose) {
        var _eventBindings = _isVerbose ? _eventBindingsVerbose : _eventBindingsShorthand;

        function _getSyntaxTestWidgetClass(_children) {
          return _getTestWidgetClass(_eventBindings, _children);
        }

        function _getSyntaxTestWidgetClassInstance(_children, _nodes, _instanceProperties) {
          return _getTestWidgetClassInstance(_eventBindings, _children, _nodes, _instanceProperties);
        }

        function _generateFireTests(_type) {
          var _fireEventMethodName = _type == 'node' ? 'triggerEvent' : 'fire';

          function _generateFireTest(_expectFunc, _instanceProperties) {
            return function(_continue) {
              if (!_wiredEvents || Uize.isEmpty(_wiredEvents[_type]))
                return true;

              var
                _typeEvents = _wiredEvents[_type],
                _names = _type == 'self' ? ['self'] : Uize.keys(_typeEvents),
                _testWidgetClassInstance = _getSyntaxTestWidgetClassInstance(
                  _type == 'child' ? _names : undefined,
                  _type == 'node' ? _names : undefined,
                  _instanceProperties
                ),
                _fail = function() { _continue(true) }
              ;
              _processArrayAsync(
                _names,
                function(_objectName, _nextObjectFunc) {
                  _processArrayAsync(
                    (_type == 'self' ? _wiredEvents : _typeEvents)[_objectName],
                    function(_eventName, _nextEventFunc) {
                      var
                        _objectToFireOn = _type == 'self'
                          ? _testWidgetClassInstance
                          : (_type == 'child'
                            ? _testWidgetClassInstance.children[_objectName]
                            : _testWidgetClassInstance.getNode(_objectName)
                          ),
                        _fireEvent = function(_fireHandler) {
                          _type == 'self' && !_eventName.indexOf('Changed.')
                            ? _objectToFireOn.set(
                              _eventName.slice(8),
                              _fireHandler
                            )
                            : _objectToFireOn[_fireEventMethodName]({
                              name:_eventName,
                              handler:_fireHandler
                            })
                          ;
                        },
                        _verifyEventFired = function(_nextFunc) {
                          var _handlerNotCalledTimeout = setTimeout(function() { _continue(false) }, 0); // timeout for if handler isn't called
                          _fireEvent(
                            function(_event, _source) {
                              clearTimeout(_handlerNotCalledTimeout);
                              _expectFunc.call(
                                this,
                                {
                                  _nextFunc:_nextFunc,
                                  _testWidgetClassInstance:_testWidgetClassInstance,
                                  _eventName:_eventName,
                                  _event:_event,
                                  _source:_source,
                                  _fail:_fail,
                                  _firedObject:_objectToFireOn
                                }
                              );
                            }
                          );
                        },
                        _verifyEventNotFired = function(_nextFunc) {
                          var
                            _handlerNotCalledTimeout = setTimeout(_nextFunc, 0)// timeout for when handler isn't called
                          ;

                          // Verify event is not fired
                          _fireEvent(
                            function() { // handler shouldn't get called
                              clearTimeout(_handlerNotCalledTimeout);
                              _continue(false);
                            }
                          );
                        },
                        _deferredChildrenForEventObject = !Uize.isEmpty(_deferredChildren)
                          && (_type == 'self'
                            ? _deferredChildren.self
                            : (_deferredChildren[_type] && _deferredChildren[_type][_objectName])
                          ),
                        _deferredChildrenForEvent = _deferredChildrenForEventObject && _deferredChildrenForEventObject[_eventName]
                      ;

                      _type == 'node' && _testWidgetClassInstance.met('wired');

                      if (Uize.isEmpty(_deferredChildrenForEvent)) {
                        _verifyEventFired(_nextEventFunc);
                      }
                      else {
                        _processArrayAsync(
                          _deferredChildrenForEvent,
                          function(_deferredChildName, _nextDeferredChildFunc) {
                            _verifyEventNotFired(
                              function() {
                                // Add deferred child
                                _testWidgetClassInstance.addChild(_deferredChildName, Uize.Widget);

                                _nextDeferredChildFunc();
                              }
                            );
                          },
                          function() {
                            // Verify event is fired
                            _verifyEventFired(
                              function() {
                                // Remove a random child
                                _testWidgetClassInstance.removeChild(
                                  _deferredChildrenForEvent[
                                    Math.floor(Math.random() * _deferredChildrenForEvent.length)
                                  ]
                                );

                                // Verify event is not fired & then move onto next event
                                _verifyEventNotFired(_nextEventFunc);
                              }
                            );
                          }
                        );
                      }
                    },
                    _nextObjectFunc
                  );
                },
                function() { _continue(true) }
              );
            };
          }

          return {
            title:Uize.capFirstChar(_type) + ' events are successfully fired',
            test:[
              {
                title:'Handler is called',
                test:_generateFireTest(
                  function(_data) { _data._nextFunc() }
                )
              },
              {
                title:'"this" context is widget',
                test:_generateFireTest(
                  function(_data) {
                    this == _data._testWidgetClassInstance
                      ? _data._nextFunc()
                      : _data._fail()
                    ;
                  }
                )
              },
              {
                title:'First argument is event object',
                test:_generateFireTest(
                  function(_data) {
                    _data._event.name == _data._eventName
                      ? _data._nextFunc()
                      : _data._fail()
                    ;
                  }
                )
              },
              {
                title:'Second argument is source object',
                test:_generateFireTest(
                  function(_data) {
                    _data._source == _data._firedObject
                      ? _data._nextFunc()
                      : _data._fail()
                    ;
                  }
                )
              }
            ]
          };
        }

        return {
          title:(_isVerbose ? 'Verbose' : 'Shorthand') + ' Syntax',
          test:[
            {
              title:'Widget class is a function (not null)',
              test:function() { return this.expectFunction(_getSyntaxTestWidgetClass()) }
            },
            {
              title:'Widget instance is an object (not null)',
              test:function() { return this.expectObject(_getSyntaxTestWidgetClassInstance()) }
            },
            _generateFireTests('self'),
            _generateFireTests('child'),
            {
              title:'Node events do not fire before widget is wired',
              test:function(_continue) {
                if (!_wiredEvents || Uize.isEmpty(_wiredEvents.node))
                  return true;

                var
                  _nodeNames = Uize.keys(_wiredEvents.node),
                  _testWidgetClassInstance = _getSyntaxTestWidgetClassInstance(null, _nodeNames)
                ;
                _processArrayAsync(
                  _nodeNames,
                  function(_nodeName, _nextNodeFunc) {
                    _processArrayAsync(
                      _wiredEvents.node[_nodeName],
                      function(_eventName, _nextEventFunc) {
                        // The handler shouldn't be called which means we won't continue to the next node,
                        // so, set up a timeout to continue on if the handler is not called.
                        var _notFiredTimeout = setTimeout(_nextEventFunc, 0);
                        _testWidgetClassInstance.getNode(_nodeName).triggerEvent({
                          name:_eventName,
                          handler:function() {
                            clearTimeout(_notFiredTimeout);
                            _continue(false); // we should never get here
                          }
                        });
                      },
                      _nextNodeFunc
                    );
                  },
                  function() { _continue(true) }
                );
              }
            },
            _generateFireTests('node')
          ]
        };
      }

      return {
        title:_title,
        test:[
          _generateSyntaxTests(),
          _generateSyntaxTests(true)
        ]
      };
    }

    return _class.declare({
      set:{
        test:[
          {
            title:'Set the global window object',
            test:function() {
              _originalWindow = _global.window;
              _global.window = _global;  // For Uize.Dom.Basics
              return true;
            }
          },
          Uize.Test.requiredModulesTest ([
            'Uize.Widget',
            'Uize.Class.mChildObjectEventBindings'
          ]),
          {
            title:'Empty bindings',
            test:[
              _generateTest('When no declarative event bindings are specified, no events are wired'),
              _generateTest('When empty event bindings is specified, no events are wired')
            ]
          },
          {
            title:'Child Events',
            test:[
              _generateTest(
                'When a single child with a single event binding is declared, only that child event is bound',
                {
                  'foo:Click':_defaultHandler
                },
                {
                  foo:{
                    Click:_defaultHandler
                  }
                },
                {
                  child:{
                    foo:['Click']
                  }
                }
              ),
              _generateTest(
                'When a single child with multiple event bindings are declared, only the events for that child are bound',
                Uize.lookup(
                  [
                    'foo:Click',
                    'foo:Changed.value',
                    'foo:Changed.bar'
                  ],
                  _defaultHandler
                ),
                {
                  foo:{
                    Click:_defaultHandler,
                    'Changed.value':_defaultHandler,
                    'Changed.bar':_defaultHandler
                  }
                },
                {
                  child:{
                    foo:['Click', 'Changed.value', 'Changed.bar']
                  }
                }
              ),
              _generateTest(
                'When a multiple children each with a single event binding are declared, only the one event for each child is bound',
                Uize.lookup(
                  [
                    'foo:Changed.bar',
                    'lorem:Changed.ipsum'
                  ],
                  _defaultHandler
                ),
                {
                  foo:{
                    'Changed.bar':_defaultHandler
                  },
                  lorem:{
                    'Changed.ipsum':_defaultHandler
                  }
                },
                {
                  child:{
                    foo:['Changed.bar'],
                    lorem:['Changed.ipsum']
                  }
                }
              ),
              _generateTest(
                'When a multiple children each with multiple event bindings are declared, only the events for those children are bound',
                Uize.lookup(
                  [
                    'foo:Click',
                    'lorem:Changed.ipsum',
                    'foo:Changed.value',
                    'lorem:Changed.dolor',
                    'foo:Changed.bar',
                    'a:Changed.b',
                    'a:c',
                    'a:d',
                    'a:Changed.e'
                  ],
                  _defaultHandler
                ),
                {
                  foo:{
                    Click:_defaultHandler,
                    'Changed.value':_defaultHandler,
                    'Changed.bar':_defaultHandler
                  },
                  lorem:{
                    'Changed.ipsum':_defaultHandler,
                    'Changed.dolor':_defaultHandler
                  },
                  a:{
                    'Changed.b':_defaultHandler,
                    'c':_defaultHandler,
                    'd':_defaultHandler,
                    'Changed.e':_defaultHandler
                  }
                },
                {
                  child:{
                    foo:['Click', 'Changed.value', 'Changed.bar'],
                    lorem:['Changed.ipsum', 'Changed.dolor'],
                    a:['Changed.b', 'c', 'd', 'Changed.e']
                  }
                }
              ),
              {
                title:'Required Children',
                test:[
                  _generateTest(
                    'When required is left unspecified for a child event binding, the event is fired normally (w/ no errors)',
                    {
                      'foo:Click':{
                          handler:_defaultHandler
                        }
                    },
                    {
                      foo:{
                        Click:{
                          handler:_defaultHandler
                        }
                      }
                    },
                    {
                      child:{
                        foo:['Click']
                      }
                    }
                  ),
                  _generateTest(
                    'When a single required child for a child event binding is declared, the event is not fired until the child is added',
                    {
                      'foo:Click':{
                          handler:_defaultHandler,
                          required:['baz']
                        }
                    },
                    {
                      foo:{
                        Click:{
                          handler:_defaultHandler,
                          required:['baz']
                        }
                      }
                    },
                    {
                      child:{
                        foo:['Click']
                      }
                    },
                    {
                      child:{
                        foo:{
                          Click:['baz']
                        }
                      }
                    }
                  ),
                  _generateTest(
                    'When multiple required children for a child event binding are declared, the event is not fired until all the children are added (and order doesn\'t matter)',
                    {
                      'foo:Click':{
                        handler:_defaultHandler,
                        required:['baz', 'bat', 'baf']
                      }
                    },
                    {
                      foo:{
                        Click:{
                          handler:_defaultHandler,
                          required:['baz', 'bat', 'baf']
                        }
                      }
                    },
                    {
                      child:{
                        foo:['Click']
                      }
                    },
                    {
                      child:{
                        foo:{
                          Click:['bat', 'baf', 'baz']
                        }
                      }
                    }
                  )
                ]
              },
              {
                title:'Conditional firing (fireIf)',
                test:Uize.push(
                  Uize.map(
                    [
                      {
                        title:'When fireIf is undefined and widget is disabled, child event is fired',
                        state:{enabled:false}
                      },
                      {
                        title:'When fireIf is undefined and widget is busy, child event is fired',
                        state:{busy:true}
                      },
                      {
                        title:'When fireIf evaluates to true, child event is fired',
                        fireIf:'foo',
                        state:{foo:true}
                      }
                    ],
                    function(_testInfo) {
                      return {
                        title:_testInfo.title,
                        test:function(_continue) {
                          var
                            _testWidgetClassInstance = _getTestWidgetClassInstance(
                              {
                                'foo:Click':{
                                  handler:function() {
                                    clearTimeout(_notFiredTimeout);
                                    _continue(true);
                                  },
                                  fireIf:_testInfo.fireIf
                                }
                              },
                              ['foo'],
                              null,
                              _testInfo.state
                            ),
                            _notFiredTimeout
                          ;

                          _notFiredTimeout = setTimeout(
                            function() { _continue(false) },
                            0
                          );

                          _testWidgetClassInstance.children.foo.fire('Click');
                        }
                      };
                    }
                  ),
                  [
                    {
                      title:'When fireIf evaluates to false, child event isn\'t fired',
                      test:function(_continue) {
                        var
                          _testWidgetClassInstance = _getTestWidgetClassInstance(
                            {
                              'foo:Click':{
                                handler:function() {
                                  clearTimeout(_notFiredTimeout);
                                  _continue(false);
                                },
                                fireIf:'foo'
                              }
                            },
                            ['foo'],
                            null
                          ),
                          _notFiredTimeout
                        ;

                        // fire child widget event (shouldn't actually be handled by widget)
                        _testWidgetClassInstance.children.foo.fire('Click');

                        _notFiredTimeout = setTimeout(
                          function() { _continue(true) },
                          0
                        );
                      }
                    }
                  ]
                )
              }
            ]
          },
          {
            title:'Self Events',
            test:[
              _generateTest(
                'When a single event binding is declared, only that event is bound',
                Uize.lookup(
                  [
                    ':Click'
                  ],
                  _defaultHandler
                ),
                {
                  '':{
                    Click:_defaultHandler
                  }
                },
                {
                  self:['Click']
                }
              ),
              _generateTest(
                'When multiple events binding are declared, only those events are bound',
                {
                  ':Click':_defaultHandler,
                  ':Changed.value':_defaultChangedHandler
                },
                {
                  '':{
                    Click:_defaultHandler,
                    'Changed.value':_defaultChangedHandler
                  }
                },
                {
                  self:['Click', 'Changed.value']
                }
              ),
              _generateTest(
                'When a single required child for a self event binding is declared, the event is not fired until the child is added',
                {
                  ':Click':{
                    handler:_defaultHandler,
                    required:['baz']
                  }
                },
                {
                  '':{
                    Click:{
                      handler:_defaultHandler,
                      required:['baz']
                    }
                  }
                },
                {
                  self:['Click']
                },
                {
                  self:{
                    Click:['baz']
                  }
                }
              ),
              _generateTest(
                'When multiple required children for a self event binding are declared, the event is not fired until all the children are added (and order doesn\'t matter)',
                {
                  ':Click':{
                    handler:_defaultHandler,
                    required:['baz', 'bat', 'baf']
                  }
                },
                {
                  '':{
                    Click:{
                      handler:_defaultHandler,
                      required:['baz', 'bat', 'baf']
                    }
                  }
                },
                {
                  self:['Click']
                },
                {
                  self:{
                    Click:['bat', 'baf', 'baz']
                  }
                }
              ),
              {
                title:'Conditional firing (fireIf)',
                test:Uize.push(
                  Uize.map(
                    [
                      {
                        title:'When fireIf is undefined and widget is disabled, self event is fired',
                        state:{enabled:false}
                      },
                      {
                        title:'When fireIf is undefined and widget is busy, self event is fired',
                        state:{busy:true}
                      },
                      {
                        title:'When fireIf evaluates to true, self event is fired',
                        fireIf:'foo',
                        state:{foo:true}
                      }
                    ],
                    function(_testInfo) {
                      return {
                        title:_testInfo.title,
                        test:function(_continue) {
                          var
                            _testWidgetClassInstance = _getTestWidgetClassInstance(
                              {
                                ':Click':{
                                  handler:function() {
                                    clearTimeout(_notFiredTimeout);
                                    _continue(true);
                                  },
                                  fireIf:_testInfo.fireIf
                                }
                              },
                              null,
                              null,
                              _testInfo.state
                            ),
                            _notFiredTimeout
                          ;

                          _notFiredTimeout = setTimeout(
                            function() { _continue(false) },
                            0
                          );

                          _testWidgetClassInstance.fire('Click');
                        }
                      };
                    }
                  ),
                  [
                    {
                      title:'When fireIf evaluates to false, self event isn\'t fired',
                      test:function(_continue) {
                        var
                          _testWidgetClassInstance = _getTestWidgetClassInstance(
                            {
                              ':Click':{
                                handler:function() {
                                  clearTimeout(_notFiredTimeout);
                                  _continue(false);
                                },
                                fireIf:'foo'
                              }
                            },
                            null,
                            null
                          ),
                          _notFiredTimeout
                        ;

                        // fire child widget event (shouldn't actually be handled by widget)
                        _testWidgetClassInstance.fire('Click');

                        _notFiredTimeout = setTimeout(
                          function() { _continue(true) },
                          0
                        );
                      }
                    }
                  ]
                )
              }
            ]
          },
          {
            title:'Node Events',
            test:[
              _generateTest(
                'When a single node with a single event binding is declared, only that node event is bound',
                Uize.lookup(
                  [
                    '#foo:click'
                  ],
                  _defaultHandler
                ),
                {
                  '#foo':{
                    click:_defaultHandler
                  }
                },
                {
                  node:{
                    foo:['click']
                  }
                }
              ),
              _generateTest(
                'When a single node with multiple event bindings are declared, only the events for that node are bound',
                Uize.lookup(
                  [
                    '#foo:click',
                    '#foo:mousemove',
                    '#foo:change'
                  ],
                  _defaultHandler
                ),
                {
                  '#foo':{
                    click:_defaultHandler,
                    mousemove:_defaultHandler,
                    change:_defaultHandler
                  }
                },
                {
                  node:{
                    foo:['click', 'mousemove', 'change']
                  }
                }
              ),
              _generateTest(
                'When a multiple nodes each with a single event binding are declared, only the one event for each node is bound',
                Uize.lookup(
                  [
                    '#foo:load',
                    '#lorem:unload'
                  ],
                  _defaultHandler
                ),
                {
                  '#foo':{
                    load:_defaultHandler
                  },
                  '#lorem':{
                    unload:_defaultHandler
                  }
                },
                {
                  node:{
                    foo:['load'],
                    lorem:['unload']
                  }
                }
              ),
              _generateTest(
                'When a multiple nodes each with multiple event bindings are declared, only the events for those nodes are bound',
                Uize.lookup(
                  [
                    '#a:focus',
                    '#foo:click',
                    '#lorem:keypress',
                    '#a:change',
                    '#foo:mouseover',
                    '#a:blur',
                    '#foo:keydown',
                    '#lorem:keyup',
                    '#a:load'
                  ],
                  _defaultHandler
                ),
                {
                  '#foo':{
                    click:_defaultHandler,
                    mouseover:_defaultHandler,
                    keydown:_defaultHandler
                  },
                  '#lorem':{
                    keypress:_defaultHandler,
                    keyup:_defaultHandler
                  },
                  '#a':{
                    load:_defaultHandler,
                    blur:_defaultHandler,
                    focus:_defaultHandler,
                    change:_defaultHandler
                  }
                },
                {
                  node:{
                    foo:['click', 'mouseover', 'keydown'],
                    lorem:['keypress', 'keyup'],
                    a:['load', 'blur', 'focus', 'change']
                  }
                }
              ),
              _generateTest(
                'When root node with a single event binding is declared, only that node event is bound',
                Uize.lookup(
                  [
                    '#:click'
                  ],
                  _defaultHandler
                ),
                {
                  '#':{
                    click:_defaultHandler
                  }
                },
                {
                  node:{
                    '':['click']
                  }
                }
              ),
              _generateTest(
                'When root node with multiple event bindings are declared, only the events for the node are bound',
                Uize.lookup(
                  [
                    '#:click',
                    '#:mousemove'
                  ],
                  _defaultHandler
                ),
                {
                  '#':{
                    click:_defaultHandler,
                    mousemove:_defaultHandler
                  }
                },
                {
                  node:{
                    '':['click', 'mousemove']
                  }
                }
              ),
              {
                title:'Required children',
                test:[
                  _generateTest(
                    'When required is left unspecified for a node event binding, the event is fired normally (w/ no errors)',
                    {
                      '#foo:click':{
                        handler:_defaultHandler
                      }
                    },
                    {
                      '#foo':{
                        click:{
                          handler:_defaultHandler
                        }
                      }
                    },
                    {
                      node:{
                        foo:['click']
                      }
                    }
                  ),
                  _generateTest(
                    'When a single required child for a node event binding is declared, the event is not fired until the child is added',
                    {
                      '#foo:click':{
                        handler:_defaultHandler,
                        required:['baz']
                      }
                    },
                    {
                      '#foo':{
                        click:{
                          handler:_defaultHandler,
                          required:['baz']
                        }
                      }
                    },
                    {
                      node:{
                        foo:['click']
                      }
                    },
                    {
                      node:{
                        foo:{
                          click:['baz']
                        }
                      }
                    }
                  ),
                  _generateTest(
                    'When multiple required children for a node event binding are declared, the event is not fired until all the children are added (and order doesn\'t matter)',
                    {
                      '#foo:click':{
                        handler:_defaultHandler,
                        required:['baz', 'bat', 'baf']
                      }
                    },
                    {
                      '#foo':{
                        click:{
                          handler:_defaultHandler,
                          required:['baz', 'bat', 'baf']
                        }
                      }
                    },
                    {
                      node:{
                        foo:['click']
                      }
                    },
                    {
                      node:{
                        foo:{
                          click:['bat', 'baf', 'baz']
                        }
                      }
                    }
                  )
                ]
              },
              {
                title:'Conditional Firing (fireIf)',
                test:Uize.push(
                  Uize.map(
                    [
                      {
                        title:'When fireIf is unspecified, the wired DOM event doesn\'t fire if busy or disabled'
                      },
                      {
                        title:'When fireIf is null, the wired DOM event doesn\'t fire if busy or disabled',
                        fireIf:null
                      },
                      {
                        title:'When fireIf is undefined, the wired DOM event doesn\'t fire if busy or disabled',
                        fireIf:undefined
                      },
                      {
                        title:'When fireIf is empty string, the wired DOM event doesn\'t fire if busy or disabled',
                        fireIf:''
                      }
                    ],
                    function (_eventBindingsInfo) {
                      return {
                        title:_eventBindingsInfo.title,
                        test:function(_continue) {
                          var
                            _testWidgetClassInstance = _getTestWidgetClassInstance(
                              {
                                '#myNode:click':Uize.copyInto(
                                  {
                                    handler:function() {
                                      clearTimeout(_notFiredTimeout);
                                      _continue(false);
                                    }
                                  },
                                  _eventBindingsInfo.fireIf ? {fireIf:_eventBindingsInfo.fireIf} : null
                                )
                              },
                              null,
                              ['myNode']
                            ),
                            _notFiredTimeout
                          ;

                          _testWidgetClassInstance.met('wired');

                          // set widget to busy or disabled
                          _testWidgetClassInstance.set(
                            Math.floor(Math.random() * 2)
                              ? {enabled:false}
                              : {busy:true}
                          );

                          // fire DOM node event (shouldn't actually be handled by widget)
                          _testWidgetClassInstance.getNode('myNode').triggerEvent({
                            name:'click'
                          });

                          _notFiredTimeout = setTimeout(
                            function() { _continue(true) },
                            0
                          );
                        }
                      };
                    }
                  ),
                  [
                    {
                      title:'When fireIf is specified, the wired DOM event doesn\'t fire if the condition hasn\'t been met',
                      test:function(_continue) {
                        var
                          _testWidgetClassInstance = _getTestWidgetClassInstance(
                            {
                              '#myNode:click':{
                                handler:function() {
                                  clearTimeout(_notFiredTimeout);
                                  _continue(false);
                                },
                                fireIf:'foo'
                              }
                            },
                            null,
                            ['myNode']
                          ),
                          _notFiredTimeout
                        ;

                        _testWidgetClassInstance.met('wired');

                        // fire DOM node event (shouldn't actually be handled by widget)
                        _testWidgetClassInstance.getNode('myNode').triggerEvent({
                          name:'click'
                        });

                        _notFiredTimeout = setTimeout(
                          function() { _continue(true) },
                          0
                        );
                      }
                    },
                    {
                      title:'When fireIf is specified, the wired DOM event fires when the condition has been met',
                      test:function(_continue) {
                        var
                          _testWidgetClassInstance = _getTestWidgetClassInstance(
                            {
                              '#myNode:click':{
                                handler:function() {
                                  clearTimeout(_notFiredTimeout);
                                  _continue(true);
                                },
                                fireIf:'foo'
                              }
                            },
                            null,
                            ['myNode']
                          ),
                          _notFiredTimeout
                        ;

                        _testWidgetClassInstance.met('wired');
                        _testWidgetClassInstance.met('foo');

                        _notFiredTimeout = setTimeout(
                          function() { _continue(false) },
                          0
                        );

                        // fire DOM node event (shouldn't actually be handled by widget)
                        _testWidgetClassInstance.getNode('myNode').triggerEvent({
                          name:'click'
                        });
                      }
                    }
                  ]
                )
              }
            ]
          },
          {
            title:'Potential Collision Events',
            test:[
              _generateTest(
                'When events for a child and node with the same name are declared, the proper events are bound',
                Uize.lookup(
                  [
                    'foo:Click',
                    'foo:Changed.bar',
                    '#foo:click',
                    '#foo:unload',
                    '#lorem:change',
                    'lorem:Changed.ipsum'
                  ],
                  _defaultHandler
                ),
                {
                  'foo':{
                    Click:_defaultHandler,
                    'Changed.bar':_defaultHandler
                  },
                  '#foo':{
                    click:_defaultHandler,
                    unload:_defaultHandler
                  },
                  '#lorem':{
                    change:_defaultHandler
                  },
                  'lorem':{
                    'Changed.ipsum':_defaultHandler
                  }
                },
                {
                  child:{
                    'foo':['Click', 'Changed.bar'],
                    'lorem':['Changed.ipsum']
                  },
                  node:{
                    'foo':['click', 'unload'],
                    'lorem':['change']
                  }
                }
              ),
              _generateTest(
                'When events for a self and root node with the same name are declared, the proper events are bound',
                Uize.copyInto(
                  Uize.lookup(
                    [
                      ':Click',
                      '#:click',
                      '#:unload'
                    ],
                    _defaultHandler
                  ),
                  {
                    ':Changed.bar':_defaultChangedHandler
                  }
                ),
                {
                  '':{
                    Click:_defaultHandler,
                    'Changed.bar':_defaultChangedHandler
                  },
                  '#':{
                    click:_defaultHandler,
                    unload:_defaultHandler
                  }
                },
                {
                  self:['Click', 'Changed.bar'],
                  node:{
                    '':['click', 'unload']
                  }
                }
              )
            ]
          },
          {
            title:'All Event Types',
            test:[
              _generateTest(
                'When all 3 types of events are declared, some with required blocks, the proper events are bound and fire at the right time',
                {
                  ':Click':{
                    handler:_defaultHandler,
                    required:['green']
                  },
                  ':Changed.bar':_defaultChangedHandler,
                  '#:click':_defaultHandler,
                  '#:unload':_defaultHandler,
                  '#lorem:change':{
                    handler:_defaultHandler,
                    required:['blah']
                  },
                  'lorem:Changed.ipsum':_defaultHandler,
                  'foo:Click':_defaultHandler,
                  'foo:Changed.bar':{
                    handler:_defaultHandler,
                    required:['hiya']
                  },
                  '#foo:click':_defaultHandler,
                  '#foo:unload':_defaultHandler,
                  '#a:load':_defaultHandler,
                  '#a:blur':{
                    handler:_defaultHandler,
                    required:['red','blue']
                  },
                  '#a:focus':_defaultHandler,
                  '#a:change':_defaultHandler
                },
                {
                  '':{
                    Click:{
                      handler:_defaultHandler,
                      required:['green']
                    },
                    'Changed.bar':_defaultChangedHandler
                  },
                  '#':{
                    click:_defaultHandler,
                    unload:_defaultHandler
                  },
                  '#lorem':{
                    change:{
                      handler:_defaultHandler,
                      required:['blah']
                    }
                  },
                  'lorem':{
                    'Changed.ipsum':_defaultHandler
                  },
                  'foo':{
                    Click:_defaultHandler,
                    'Changed.bar':{
                      handler:_defaultHandler,
                      required:['hiya']
                    }
                  },
                  '#foo':{
                    click:_defaultHandler,
                    unload:_defaultHandler
                  },
                  '#a':{
                    load:_defaultHandler,
                    blur:{
                      handler:_defaultHandler,
                      required:['red','blue']
                    },
                    focus:_defaultHandler,
                    change:_defaultHandler
                  }
                },
                {
                  self:['Click', 'Changed.bar'],
                  child:{
                    'foo':['Click', 'Changed.bar'],
                    'lorem':['Changed.ipsum']
                  },
                  node:{
                    '':['click', 'unload'],
                    'foo':['click', 'unload'],
                    'lorem':['change'],
                    a:['load', 'blur', 'focus', 'change']
                  }
                },
                {
                  node:{
                    lorem:{
                      change:['blah']
                    },
                    a:{
                      blur:['red','blue']
                    }
                  },
                  child:{
                    foo:{
                      'Changed.bar':['hiya']
                    }
                  },
                  self:{
                    Click:['green']
                  }
                }
              )
            ]
          },
          {
            title:'Edge cases',
            test:[
              {
                title:'When a bound child is removed, a fired event on the child is properly handled (no errors & not fired on parent)',
                test:function(_continue) {
                  var
                    _widget = _getTestWidgetClassInstance(
                      {
                        'childA:Click':function() { _continue(false) }
                      },
                      ['childA']
                    ),
                    _childToRemove = _widget.children.childA // keep reference so we'll have it after removal
                  ;

                  _widget.removeChild(_childToRemove);

                  // fire child event (shouldn't actually be handled by the parent)
                  _childToRemove.fire('Click');

                  setTimeout(
                    function() { _continue(true) },
                    0
                  );
                }
              },
              {
                title:'When a bound child is removed, and a new same-named child is re-added, a child event is handled by the parent',
                test:function(_continue) {
                  var
                    _widget = _getTestWidgetClassInstance(
                      {
                        'childA:Click':function() {
                          clearTimeout(_failTimeout);
                          _continue(true);
                        }
                      },
                      ['childA']
                    ),
                    _failTimeout
                  ;

                  // remove child
                  _widget.removeChild('childA');

                  // add new same-named child
                  var _newChild = _widget.addChild('childA', Uize.Widget);

                  // this shouldn't happen because the handler should be rebound when new child was added
                  _failTimeout = setTimeout(
                    function() { _continue(false) },
                    0
                  );

                  _newChild.fire('Click'); // fire child event (should be handled by parent)
                }
              },
              {
                title:'When a subclass declares the same child/event combination, the base class\' handler is called',
                test:function(_continue) {
                  var
                    _WidgetClass = _getTestWidgetClass(
                      {
                        'childA:Click':function() {
                          clearTimeout(_failTimeout);
                          _continue(true);
                        }
                      },
                      ['childA']
                    ),
                    _WidgetSubclass = _WidgetClass.subclass(
                      _Uize.pairUp(
                        _eventBindingsFunctionName,
                        {
                          'childA:Click':_Uize.nop
                        }
                      )
                    ),
                    _failTimeout
                  ;

                  // this shouldn't happen because the base class' handler should be called
                  _failTimeout = setTimeout(
                    function() { _continue(false) },
                    0
                  );

                  _WidgetSubclass().children.childA.fire('Click');
                }
              }
            ]
          },
          {
            title:'Restore the global window object',
            test:function() {
              _global.window = _originalWindow;
              return true;
            }
          }
        ]
      }
    });
  }
});