Merge branch 'MDL-32509' of git://github.com/danpoltawski/moodle
[moodle.git] / lib / yui / 3.5.0 / build / test / test-debug.js
blob79372910be4fad981a8fee49f015f2804b17c194
1 /*
2 YUI 3.5.0 (build 5089)
3 Copyright 2012 Yahoo! Inc. All rights reserved.
4 Licensed under the BSD License.
5 http://yuilibrary.com/license/
6 */
7 YUI.add('test', function(Y) {
9 /**
10  * YUI Test Framework
11  * @module test
12  */
14 /**
15  * The root namespace for YUI Test.
16  * @class Test
17  * @static
18  */
20 var YUITest = {
21     version: "3.5.0"
24 Y.namespace('Test');
27 //Using internal YUI methods here
28 YUITest.Object = Y.Object;
29 YUITest.Array = Y.Array;
30 YUITest.Util = {
31     mix: Y.mix,
32     JSON: Y.JSON
35 /**
36  * Simple custom event implementation.
37  * @namespace Test
38  * @class EventTarget
39  * @constructor
40  */
41 YUITest.EventTarget = function(){
43     /**
44      * Event handlers for the various events.
45      * @type Object
46      * @private
47      * @property _handlers
48      * @static
49      */
50     this._handlers = {};
53     
54 YUITest.EventTarget.prototype = {
56     //restore prototype
57     constructor: YUITest.EventTarget,
58             
59     //-------------------------------------------------------------------------
60     // Event Handling
61     //-------------------------------------------------------------------------
62     
63     /**
64      * Adds a listener for a given event type.
65      * @param {String} type The type of event to add a listener for.
66      * @param {Function} listener The function to call when the event occurs.
67      * @return {void}
68      * @method attach
69      */
70     attach: function(type, listener){
71         if (typeof this._handlers[type] == "undefined"){
72             this._handlers[type] = [];
73         }
75         this._handlers[type].push(listener);
76     },
77     
78     /**
79      * Adds a listener for a given event type.
80      * @param {String} type The type of event to add a listener for.
81      * @param {Function} listener The function to call when the event occurs.
82      * @return {void}
83      * @method subscribe
84      * @deprecated
85      */
86     subscribe: function(type, listener){
87         this.attach.apply(this, arguments);
88     },
89     
90     /**
91      * Fires an event based on the passed-in object.
92      * @param {Object|String} event An object with at least a 'type' attribute
93      *      or a string indicating the event name.
94      * @return {void}
95      * @method fire
96      */    
97     fire: function(event){
98         if (typeof event == "string"){
99             event = { type: event };
100         }
101         if (!event.target){
102             event.target = this;
103         }
104         
105         if (!event.type){
106             throw new Error("Event object missing 'type' property.");
107         }
108         
109         if (this._handlers[event.type] instanceof Array){
110             var handlers = this._handlers[event.type];
111             for (var i=0, len=handlers.length; i < len; i++){
112                 handlers[i].call(this, event);
113             }
114         }            
115     },
117     /**
118      * Removes a listener for a given event type.
119      * @param {String} type The type of event to remove a listener from.
120      * @param {Function} listener The function to remove from the event.
121      * @return {void}
122      * @method detach
123      */
124     detach: function(type, listener){
125         if (this._handlers[type] instanceof Array){
126             var handlers = this._handlers[type];
127             for (var i=0, len=handlers.length; i < len; i++){
128                 if (handlers[i] === listener){
129                     handlers.splice(i, 1);
130                     break;
131                 }
132             }
133         }            
134     },
135     
136     /**
137      * Removes a listener for a given event type.
138      * @param {String} type The type of event to remove a listener from.
139      * @param {Function} listener The function to remove from the event.
140      * @return {void}
141      * @method unsubscribe
142      * @deprecated
143      */
144     unsubscribe: function(type, listener){
145         this.detach.apply(this, arguments);          
146     }    
150     
152  * A test suite that can contain a collection of TestCase and TestSuite objects.
153  * @param {String||Object} data The name of the test suite or an object containing
154  *      a name property as well as setUp and tearDown methods.
155  * @namespace Test
156  * @class TestSuite
157  * @constructor
158  */
159 YUITest.TestSuite = function (data) {
161     /**
162      * The name of the test suite.
163      * @type String
164      * @property name
165      */
166     this.name = "";
168     /**
169      * Array of test suites and test cases.
170      * @type Array
171      * @property items
172      * @private
173      */
174     this.items = [];
176     //initialize the properties
177     if (typeof data == "string"){
178         this.name = data;
179     } else if (data instanceof Object){
180         for (var prop in data){
181             if (data.hasOwnProperty(prop)){
182                 this[prop] = data[prop];
183             }
184         }
185     }
187     //double-check name
188     if (this.name === ""){
189         this.name = "testSuite" + (+new Date());
190     }
193     
194 YUITest.TestSuite.prototype = {
195     
196     //restore constructor
197     constructor: YUITest.TestSuite,
198     
199     /**
200      * Adds a test suite or test case to the test suite.
201      * @param {Test.TestSuite||YUITest.TestCase} testObject The test suite or test case to add.
202      * @return {Void}
203      * @method add
204      */
205     add : function (testObject) {
206         if (testObject instanceof YUITest.TestSuite || testObject instanceof YUITest.TestCase) {
207             this.items.push(testObject);
208         }
209         return this;
210     },
211     
212     //-------------------------------------------------------------------------
213     // Stub Methods
214     //-------------------------------------------------------------------------
216     /**
217      * Function to run before each test is executed.
218      * @return {Void}
219      * @method setUp
220      */
221     setUp : function () {
222     },
223     
224     /**
225      * Function to run after each test is executed.
226      * @return {Void}
227      * @method tearDown
228      */
229     tearDown: function () {
230     }
231     
234  * Test case containing various tests to run.
235  * @param template An object containing any number of test methods, other methods,
236  *                 an optional name, and anything else the test case needs.
237  * @class TestCase
238  * @namespace Test
239  * @constructor
240  */
241 YUITest.TestCase = function (template) {
242     
243     /**
244      * Special rules for the test case. Possible subobjects
245      * are fail, for tests that should fail, and error, for
246      * tests that should throw an error.
247      */
248     this._should = {};
249     
250     //copy over all properties from the template to this object
251     for (var prop in template) {
252         this[prop] = template[prop];
253     }    
254     
255     //check for a valid name
256     if (typeof this.name != "string"){
257         this.name = "testCase" + (+new Date());
258     }
261         
262 YUITest.TestCase.prototype = {  
264     //restore constructor
265     constructor: YUITest.TestCase,
266     
267     /**
268      * Method to call from an async init method to
269      * restart the test case. When called, returns a function
270      * that should be called when tests are ready to continue.
271      * @method callback
272      * @return {Function} The function to call as a callback.
273      */
274     callback: function(){
275         return YUITest.TestRunner.callback.apply(YUITest.TestRunner,arguments);
276     },
278     /**
279      * Resumes a paused test and runs the given function.
280      * @param {Function} segment (Optional) The function to run.
281      *      If omitted, the test automatically passes.
282      * @return {Void}
283      * @method resume
284      */
285     resume : function (segment) {
286         YUITest.TestRunner.resume(segment);
287     },
289     /**
290      * Causes the test case to wait a specified amount of time and then
291      * continue executing the given code.
292      * @param {Function} segment (Optional) The function to run after the delay.
293      *      If omitted, the TestRunner will wait until resume() is called.
294      * @param {int} delay (Optional) The number of milliseconds to wait before running
295      *      the function. If omitted, defaults to zero.
296      * @return {Void}
297      * @method wait
298      */
299     wait : function (segment, delay){
300         
301         var actualDelay = (typeof segment == "number" ? segment : delay);
302         actualDelay = (typeof actualDelay == "number" ? actualDelay : 10000);
303     
304                 if (typeof segment == "function"){
305             throw new YUITest.Wait(segment, actualDelay);
306         } else {
307             throw new YUITest.Wait(function(){
308                 YUITest.Assert.fail("Timeout: wait() called but resume() never called.");
309             }, actualDelay);
310         }
311     },
312     
313     //-------------------------------------------------------------------------
314     // Assertion Methods
315     //-------------------------------------------------------------------------
317     /**
318      * Asserts that a given condition is true. If not, then a YUITest.AssertionError object is thrown
319      * and the test fails.
320      * @method assert
321      * @param {Boolean} condition The condition to test.
322      * @param {String} message The message to display if the assertion fails.
323      */
324     assert : function (condition, message){
325         YUITest.Assert._increment();
326         if (!condition){
327             throw new YUITest.AssertionError(YUITest.Assert._formatMessage(message, "Assertion failed."));
328         }    
329     },
330     
331     /**
332      * Forces an assertion error to occur. Shortcut for YUITest.Assert.fail().
333      * @method fail
334      * @param {String} message (Optional) The message to display with the failure.
335      */
336     fail: function (message) {    
337         YUITest.Assert.fail(message);
338     },
339     
340     //-------------------------------------------------------------------------
341     // Stub Methods
342     //-------------------------------------------------------------------------
344     /**
345      * Function to run once before tests start to run.
346      * This executes before the first call to setUp().
347      */
348     init: function(){
349         //noop
350     },
351     
352     /**
353      * Function to run once after tests finish running.
354      * This executes after the last call to tearDown().
355      */
356     destroy: function(){
357         //noop
358     },
360     /**
361      * Function to run before each test is executed.
362      * @return {Void}
363      * @method setUp
364      */
365     setUp : function () {
366         //noop
367     },
368     
369     /**
370      * Function to run after each test is executed.
371      * @return {Void}
372      * @method tearDown
373      */
374     tearDown: function () {    
375         //noop
376     }
379  * An object object containing test result formatting methods.
380  * @namespace Test
381  * @class TestFormat
382  * @static
383  */
384 YUITest.TestFormat = function(){
385     
386     /* (intentionally not documented)
387      * Basic XML escaping method. Replaces quotes, less-than, greater-than,
388      * apostrophe, and ampersand characters with their corresponding entities.
389      * @param {String} text The text to encode.
390      * @return {String} The XML-escaped text.
391      */
392     function xmlEscape(text){
393     
394         return text.replace(/[<>"'&]/g, function(value){
395             switch(value){
396                 case "<":   return "&lt;";
397                 case ">":   return "&gt;";
398                 case "\"":  return "&quot;";
399                 case "'":   return "&apos;";
400                 case "&":   return "&amp;";
401             }
402         });
403     
404     }
405         
406         
407     return {
408     
409         /**
410          * Returns test results formatted as a JSON string. Requires JSON utility.
411          * @param {Object} result The results object created by TestRunner.
412          * @return {String} A JSON-formatted string of results.
413          * @method JSON
414          * @static
415          */
416         JSON: function(results) {
417             return YUITest.Util.JSON.stringify(results);
418         },
419         
420         /**
421          * Returns test results formatted as an XML string.
422          * @param {Object} result The results object created by TestRunner.
423          * @return {String} An XML-formatted string of results.
424          * @method XML
425          * @static
426          */
427         XML: function(results) {
429             function serializeToXML(results){
430                 var xml = "<" + results.type + " name=\"" + xmlEscape(results.name) + "\"";
431                 
432                 if (typeof(results.duration)=="number"){
433                     xml += " duration=\"" + results.duration + "\"";
434                 }
435                 
436                 if (results.type == "test"){
437                     xml += " result=\"" + results.result + "\" message=\"" + xmlEscape(results.message) + "\">";
438                 } else {
439                     xml += " passed=\"" + results.passed + "\" failed=\"" + results.failed + "\" ignored=\"" + results.ignored + "\" total=\"" + results.total + "\">";
440                     for (var prop in results){
441                         if (results.hasOwnProperty(prop)){
442                             if (results[prop] && typeof results[prop] == "object" && !(results[prop] instanceof Array)){
443                                 xml += serializeToXML(results[prop]);
444                             }
445                         }
446                     }       
447                 }
449                 xml += "</" + results.type + ">";
450                 
451                 return xml;    
452             }
454             return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + serializeToXML(results);
456         },
459         /**
460          * Returns test results formatted in JUnit XML format.
461          * @param {Object} result The results object created by TestRunner.
462          * @return {String} An XML-formatted string of results.
463          * @method JUnitXML
464          * @static
465          */
466         JUnitXML: function(results) {
468             function serializeToJUnitXML(results){
469                 var xml = "";
470                     
471                 switch (results.type){
472                     //equivalent to testcase in JUnit
473                     case "test":
474                         if (results.result != "ignore"){
475                             xml = "<testcase name=\"" + xmlEscape(results.name) + "\" time=\"" + (results.duration/1000) + "\">";
476                             if (results.result == "fail"){
477                                 xml += "<failure message=\"" + xmlEscape(results.message) + "\"><![CDATA[" + results.message + "]]></failure>";
478                             }
479                             xml+= "</testcase>";
480                         }
481                         break;
482                         
483                     //equivalent to testsuite in JUnit
484                     case "testcase":
485                     
486                         xml = "<testsuite name=\"" + xmlEscape(results.name) + "\" tests=\"" + results.total + "\" failures=\"" + results.failed + "\" time=\"" + (results.duration/1000) + "\">";
487                         
488                         for (var prop in results){
489                             if (results.hasOwnProperty(prop)){
490                                 if (results[prop] && typeof results[prop] == "object" && !(results[prop] instanceof Array)){
491                                     xml += serializeToJUnitXML(results[prop]);
492                                 }
493                             }
494                         }            
495                         
496                         xml += "</testsuite>";
497                         break;
498                     
499                     //no JUnit equivalent, don't output anything
500                     case "testsuite":
501                         for (var prop in results){
502                             if (results.hasOwnProperty(prop)){
503                                 if (results[prop] && typeof results[prop] == "object" && !(results[prop] instanceof Array)){
504                                     xml += serializeToJUnitXML(results[prop]);
505                                 }
506                             }
507                         }                                                     
508                         break;
509                         
510                     //top-level, equivalent to testsuites in JUnit
511                     case "report":
512                     
513                         xml = "<testsuites>";
514                     
515                         for (var prop in results){
516                             if (results.hasOwnProperty(prop)){
517                                 if (results[prop] && typeof results[prop] == "object" && !(results[prop] instanceof Array)){
518                                     xml += serializeToJUnitXML(results[prop]);
519                                 }
520                             }
521                         }            
522                         
523                         xml += "</testsuites>";            
524                     
525                     //no default
526                 }
527                 
528                 return xml;
529          
530             }
532             return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + serializeToJUnitXML(results);
533         },
534     
535         /**
536          * Returns test results formatted in TAP format.
537          * For more information, see <a href="http://testanything.org/">Test Anything Protocol</a>.
538          * @param {Object} result The results object created by TestRunner.
539          * @return {String} A TAP-formatted string of results.
540          * @method TAP
541          * @static
542          */
543         TAP: function(results) {
544         
545             var currentTestNum = 1;
547             function serializeToTAP(results){
548                 var text = "";
549                     
550                 switch (results.type){
552                     case "test":
553                         if (results.result != "ignore"){
555                             text = "ok " + (currentTestNum++) + " - " + results.name;
556                             
557                             if (results.result == "fail"){
558                                 text = "not " + text + " - " + results.message;
559                             }
560                             
561                             text += "\n";
562                         } else {
563                             text = "#Ignored test " + results.name + "\n";
564                         }
565                         break;
566                         
567                     case "testcase":
568                     
569                         text = "#Begin testcase " + results.name + "(" + results.failed + " failed of " + results.total + ")\n";
570                                         
571                         for (var prop in results){
572                             if (results.hasOwnProperty(prop)){
573                                 if (results[prop] && typeof results[prop] == "object" && !(results[prop] instanceof Array)){
574                                     text += serializeToTAP(results[prop]);
575                                 }
576                             }
577                         }            
578                         
579                         text += "#End testcase " + results.name + "\n";
580                         
581                         
582                         break;
583                     
584                     case "testsuite":
586                         text = "#Begin testsuite " + results.name + "(" + results.failed + " failed of " + results.total + ")\n";                
587                     
588                         for (var prop in results){
589                             if (results.hasOwnProperty(prop)){
590                                 if (results[prop] && typeof results[prop] == "object" && !(results[prop] instanceof Array)){
591                                     text += serializeToTAP(results[prop]);
592                                 }
593                             }
594                         }                                                      
596                         text += "#End testsuite " + results.name + "\n";
597                         break;
599                     case "report":
600                     
601                         for (var prop in results){
602                             if (results.hasOwnProperty(prop)){
603                                 if (results[prop] && typeof results[prop] == "object" && !(results[prop] instanceof Array)){
604                                     text += serializeToTAP(results[prop]);
605                                 }
606                             }
607                         }              
608                         
609                     //no default
610                 }
611                 
612                 return text;
613          
614             }
616             return "1.." + results.total + "\n" + serializeToTAP(results);
617         }
618     
619     };
620 }();
621     
622     /**
623      * An object capable of sending test results to a server.
624      * @param {String} url The URL to submit the results to.
625      * @param {Function} format (Optiona) A function that outputs the results in a specific format.
626      *      Default is YUITest.TestFormat.XML.
627      * @constructor
628      * @namespace Test
629      * @class Reporter
630      */
631     YUITest.Reporter = function(url, format) {
632     
633         /**
634          * The URL to submit the data to.
635          * @type String
636          * @property url
637          */
638         this.url = url;
639     
640         /**
641          * The formatting function to call when submitting the data.
642          * @type Function
643          * @property format
644          */
645         this.format = format || YUITest.TestFormat.XML;
646     
647         /**
648          * Extra fields to submit with the request.
649          * @type Object
650          * @property _fields
651          * @private
652          */
653         this._fields = new Object();
654         
655         /**
656          * The form element used to submit the results.
657          * @type HTMLFormElement
658          * @property _form
659          * @private
660          */
661         this._form = null;
662     
663         /**
664          * Iframe used as a target for form submission.
665          * @type HTMLIFrameElement
666          * @property _iframe
667          * @private
668          */
669         this._iframe = null;
670     };
671     
672     YUITest.Reporter.prototype = {
673     
674         //restore missing constructor
675         constructor: YUITest.Reporter,
676     
677         /**
678          * Adds a field to the form that submits the results.
679          * @param {String} name The name of the field.
680          * @param {Variant} value The value of the field.
681          * @return {Void}
682          * @method addField
683          */
684         addField : function (name, value){
685             this._fields[name] = value;    
686         },
687         
688         /**
689          * Removes all previous defined fields.
690          * @return {Void}
691          * @method addField
692          */
693         clearFields : function(){
694             this._fields = new Object();
695         },
696     
697         /**
698          * Cleans up the memory associated with the TestReporter, removing DOM elements
699          * that were created.
700          * @return {Void}
701          * @method destroy
702          */
703         destroy : function() {
704             if (this._form){
705                 this._form.parentNode.removeChild(this._form);
706                 this._form = null;
707             }        
708             if (this._iframe){
709                 this._iframe.parentNode.removeChild(this._iframe);
710                 this._iframe = null;
711             }
712             this._fields = null;
713         },
714     
715         /**
716          * Sends the report to the server.
717          * @param {Object} results The results object created by TestRunner.
718          * @return {Void}
719          * @method report
720          */
721         report : function(results){
722         
723             //if the form hasn't been created yet, create it
724             if (!this._form){
725                 this._form = document.createElement("form");
726                 this._form.method = "post";
727                 this._form.style.visibility = "hidden";
728                 this._form.style.position = "absolute";
729                 this._form.style.top = 0;
730                 document.body.appendChild(this._form);
731             
732                 //IE won't let you assign a name using the DOM, must do it the hacky way
733                 try {
734                     this._iframe = document.createElement("<iframe name=\"yuiTestTarget\" />");
735                 } catch (ex){
736                     this._iframe = document.createElement("iframe");
737                     this._iframe.name = "yuiTestTarget";
738                 }
739     
740                 this._iframe.src = "javascript:false";
741                 this._iframe.style.visibility = "hidden";
742                 this._iframe.style.position = "absolute";
743                 this._iframe.style.top = 0;
744                 document.body.appendChild(this._iframe);
745     
746                 this._form.target = "yuiTestTarget";
747             }
748     
749             //set the form's action
750             this._form.action = this.url;
751         
752             //remove any existing fields
753             while(this._form.hasChildNodes()){
754                 this._form.removeChild(this._form.lastChild);
755             }
756             
757             //create default fields
758             this._fields.results = this.format(results);
759             this._fields.useragent = navigator.userAgent;
760             this._fields.timestamp = (new Date()).toLocaleString();
761     
762             //add fields to the form
763             for (var prop in this._fields){
764                 var value = this._fields[prop];
765                 if (this._fields.hasOwnProperty(prop) && (typeof value != "function")){
766                     var input = document.createElement("input");
767                     input.type = "hidden";
768                     input.name = prop;
769                     input.value = value;
770                     this._form.appendChild(input);
771                 }
772             }
773     
774             //remove default fields
775             delete this._fields.results;
776             delete this._fields.useragent;
777             delete this._fields.timestamp;
778             
779             if (arguments[1] !== false){
780                 this._form.submit();
781             }
782         
783         }
784     
785     };
786     
787     /**
788      * Runs test suites and test cases, providing events to allowing for the
789      * interpretation of test results.
790      * @namespace Test
791      * @class TestRunner
792      * @static
793      */
794     YUITest.TestRunner = function(){
796         /*(intentionally not documented)
797          * Determines if any of the array of test groups appears
798          * in the given TestRunner filter.
799          * @param {Array} testGroups The array of test groups to
800          *      search for.
801          * @param {String} filter The TestRunner groups filter.
802          */
803         function inGroups(testGroups, filter){
804             if (!filter.length){
805                 return true;
806             } else {                
807                 if (testGroups){
808                     for (var i=0, len=testGroups.length; i < len; i++){
809                         if (filter.indexOf("," + testGroups[i] + ",") > -1){
810                             return true;
811                         }
812                     }
813                 }
814                 return false;
815             }
816         }
817     
818         /**
819          * A node in the test tree structure. May represent a TestSuite, TestCase, or
820          * test function.
821          * @param {Variant} testObject A TestSuite, TestCase, or the name of a test function.
822          * @class TestNode
823          * @constructor
824          * @private
825          */
826         function TestNode(testObject){
827         
828             /**
829              * The TestSuite, TestCase, or test function represented by this node.
830              * @type Variant
831              * @property testObject
832              */
833             this.testObject = testObject;
834             
835             /**
836              * Pointer to this node's first child.
837              * @type TestNode
838              * @property firstChild
839              */        
840             this.firstChild = null;
841             
842             /**
843              * Pointer to this node's last child.
844              * @type TestNode
845              * @property lastChild
846              */        
847             this.lastChild = null;
848             
849             /**
850              * Pointer to this node's parent.
851              * @type TestNode
852              * @property parent
853              */        
854             this.parent = null; 
855        
856             /**
857              * Pointer to this node's next sibling.
858              * @type TestNode
859              * @property next
860              */        
861             this.next = null;
862             
863             /**
864              * Test results for this test object.
865              * @type object
866              * @property results
867              */                
868             this.results = new YUITest.Results();
869             
870             //initialize results
871             if (testObject instanceof YUITest.TestSuite){
872                 this.results.type = "testsuite";
873                 this.results.name = testObject.name;
874             } else if (testObject instanceof YUITest.TestCase){
875                 this.results.type = "testcase";
876                 this.results.name = testObject.name;
877             }
878            
879         }
880         
881         TestNode.prototype = {
882         
883             /**
884              * Appends a new test object (TestSuite, TestCase, or test function name) as a child
885              * of this node.
886              * @param {Variant} testObject A TestSuite, TestCase, or the name of a test function.
887              * @return {Void}
888              */
889             appendChild : function (testObject){
890                 var node = new TestNode(testObject);
891                 if (this.firstChild === null){
892                     this.firstChild = this.lastChild = node;
893                 } else {
894                     this.lastChild.next = node;
895                     this.lastChild = node;
896                 }
897                 node.parent = this;
898                 return node;
899             }       
900         };
901     
902         /**
903          * Runs test suites and test cases, providing events to allowing for the
904          * interpretation of test results.
905          * @namespace Test
906          * @class Runner
907          * @static
908          */
909         function TestRunner(){
910         
911             //inherit from EventTarget
912             YUITest.EventTarget.call(this);
913             
914             /**
915              * Suite on which to attach all TestSuites and TestCases to be run.
916              * @type YUITest.TestSuite
917              * @property masterSuite
918              * @static
919              * @private
920              */
921             this.masterSuite = new YUITest.TestSuite("yuitests" + (new Date()).getTime());        
922     
923             /**
924              * Pointer to the current node in the test tree.
925              * @type TestNode
926              * @private
927              * @property _cur
928              * @static
929              */
930             this._cur = null;
931             
932             /**
933              * Pointer to the root node in the test tree.
934              * @type TestNode
935              * @private
936              * @property _root
937              * @static
938              */
939             this._root = null;
940             
941             /**
942              * Indicates if the TestRunner will log events or not.
943              * @type Boolean
944              * @property _log
945              * @private
946              * @static
947              */
948             this._log = true;
949             
950             /**
951              * Indicates if the TestRunner is waiting as a result of
952              * wait() being called.
953              * @type Boolean
954              * @property _waiting
955              * @private
956              * @static
957              */
958             this._waiting = false;
959             
960             /**
961              * Indicates if the TestRunner is currently running tests.
962              * @type Boolean
963              * @private
964              * @property _running
965              * @static
966              */
967             this._running = false;
968             
969             /**
970              * Holds copy of the results object generated when all tests are
971              * complete.
972              * @type Object
973              * @private
974              * @property _lastResults
975              * @static
976              */
977             this._lastResults = null;       
978             
979             /**
980              * Data object that is passed around from method to method.
981              * @type Object
982              * @private
983              * @property _data
984              * @static
985              */
986             this._context = null;
987             
988             /**
989              * The list of test groups to run. The list is represented
990              * by a comma delimited string with commas at the start and
991              * end.
992              * @type String
993              * @private
994              * @property _groups
995              * @static
996              */
997             this._groups = "";
999         }
1000         
1001         TestRunner.prototype = YUITest.Util.mix(new YUITest.EventTarget(), {
1002             
1003             /**
1004             * If true, YUITest will not fire an error for tests with no Asserts.
1005             * @prop _ignoreEmpty
1006             * @private
1007             * @type Boolean
1008             * @static
1009             */
1010             _ignoreEmpty: false,
1012             //restore prototype
1013             constructor: YUITest.TestRunner,
1014         
1015             //-------------------------------------------------------------------------
1016             // Constants
1017             //-------------------------------------------------------------------------
1018              
1019             /**
1020              * Fires when a test case is opened but before the first 
1021              * test is executed.
1022              * @event testcasebegin
1023              * @static
1024              */         
1025             TEST_CASE_BEGIN_EVENT : "testcasebegin",
1026             
1027             /**
1028              * Fires when all tests in a test case have been executed.
1029              * @event testcasecomplete
1030              * @static
1031              */        
1032             TEST_CASE_COMPLETE_EVENT : "testcasecomplete",
1033             
1034             /**
1035              * Fires when a test suite is opened but before the first 
1036              * test is executed.
1037              * @event testsuitebegin
1038              * @static
1039              */        
1040             TEST_SUITE_BEGIN_EVENT : "testsuitebegin",
1041             
1042             /**
1043              * Fires when all test cases in a test suite have been
1044              * completed.
1045              * @event testsuitecomplete
1046              * @static
1047              */        
1048             TEST_SUITE_COMPLETE_EVENT : "testsuitecomplete",
1049             
1050             /**
1051              * Fires when a test has passed.
1052              * @event pass
1053              * @static
1054              */        
1055             TEST_PASS_EVENT : "pass",
1056             
1057             /**
1058              * Fires when a test has failed.
1059              * @event fail
1060              * @static
1061              */        
1062             TEST_FAIL_EVENT : "fail",
1063             
1064             /**
1065              * Fires when a non-test method has an error.
1066              * @event error
1067              * @static
1068              */        
1069             ERROR_EVENT : "error",
1070             
1071             /**
1072              * Fires when a test has been ignored.
1073              * @event ignore
1074              * @static
1075              */        
1076             TEST_IGNORE_EVENT : "ignore",
1077             
1078             /**
1079              * Fires when all test suites and test cases have been completed.
1080              * @event complete
1081              * @static
1082              */        
1083             COMPLETE_EVENT : "complete",
1084             
1085             /**
1086              * Fires when the run() method is called.
1087              * @event begin
1088              * @static
1089              */        
1090             BEGIN_EVENT : "begin",                           
1092             //-------------------------------------------------------------------------
1093             // Test Tree-Related Methods
1094             //-------------------------------------------------------------------------
1095     
1096             /**
1097              * Adds a test case to the test tree as a child of the specified node.
1098              * @param {TestNode} parentNode The node to add the test case to as a child.
1099              * @param {Test.TestCase} testCase The test case to add.
1100              * @return {Void}
1101              * @static
1102              * @private
1103              * @method _addTestCaseToTestTree
1104              */
1105            _addTestCaseToTestTree : function (parentNode, testCase){
1106                 
1107                 //add the test suite
1108                 var node = parentNode.appendChild(testCase),
1109                     prop,
1110                     testName;
1111                 
1112                 //iterate over the items in the test case
1113                 for (prop in testCase){
1114                     if ((prop.indexOf("test") === 0 || prop.indexOf(" ") > -1) && typeof testCase[prop] == "function"){
1115                         node.appendChild(prop);
1116                     }
1117                 }
1118              
1119             },
1120             
1121             /**
1122              * Adds a test suite to the test tree as a child of the specified node.
1123              * @param {TestNode} parentNode The node to add the test suite to as a child.
1124              * @param {Test.TestSuite} testSuite The test suite to add.
1125              * @return {Void}
1126              * @static
1127              * @private
1128              * @method _addTestSuiteToTestTree
1129              */
1130             _addTestSuiteToTestTree : function (parentNode, testSuite) {
1131                 
1132                 //add the test suite
1133                 var node = parentNode.appendChild(testSuite);
1134                 
1135                 //iterate over the items in the master suite
1136                 for (var i=0; i < testSuite.items.length; i++){
1137                     if (testSuite.items[i] instanceof YUITest.TestSuite) {
1138                         this._addTestSuiteToTestTree(node, testSuite.items[i]);
1139                     } else if (testSuite.items[i] instanceof YUITest.TestCase) {
1140                         this._addTestCaseToTestTree(node, testSuite.items[i]);
1141                     }                   
1142                 }            
1143             },
1144             
1145             /**
1146              * Builds the test tree based on items in the master suite. The tree is a hierarchical
1147              * representation of the test suites, test cases, and test functions. The resulting tree
1148              * is stored in _root and the pointer _cur is set to the root initially.
1149              * @return {Void}
1150              * @static
1151              * @private
1152              * @method _buildTestTree
1153              */
1154             _buildTestTree : function () {
1155             
1156                 this._root = new TestNode(this.masterSuite);
1157                 //this._cur = this._root;
1158                 
1159                 //iterate over the items in the master suite
1160                 for (var i=0; i < this.masterSuite.items.length; i++){
1161                     if (this.masterSuite.items[i] instanceof YUITest.TestSuite) {
1162                         this._addTestSuiteToTestTree(this._root, this.masterSuite.items[i]);
1163                     } else if (this.masterSuite.items[i] instanceof YUITest.TestCase) {
1164                         this._addTestCaseToTestTree(this._root, this.masterSuite.items[i]);
1165                     }                   
1166                 }            
1167             
1168             }, 
1169         
1170             //-------------------------------------------------------------------------
1171             // Private Methods
1172             //-------------------------------------------------------------------------
1173             
1174             /**
1175              * Handles the completion of a test object's tests. Tallies test results 
1176              * from one level up to the next.
1177              * @param {TestNode} node The TestNode representing the test object.
1178              * @return {Void}
1179              * @method _handleTestObjectComplete
1180              * @private
1181              */
1182             _handleTestObjectComplete : function (node) {
1183                 var parentNode;
1184                 
1185                 if (node && (typeof node.testObject == "object")) {
1186                     parentNode = node.parent;
1187                 
1188                     if (parentNode){
1189                         parentNode.results.include(node.results); 
1190                         parentNode.results[node.testObject.name] = node.results;
1191                     }
1192                 
1193                     if (node.testObject instanceof YUITest.TestSuite){
1194                         this._execNonTestMethod(node, "tearDown", false);
1195                         node.results.duration = (new Date()) - node._start;
1196                         this.fire({ type: this.TEST_SUITE_COMPLETE_EVENT, testSuite: node.testObject, results: node.results});
1197                     } else if (node.testObject instanceof YUITest.TestCase){
1198                         this._execNonTestMethod(node, "destroy", false);
1199                         node.results.duration = (new Date()) - node._start;
1200                         this.fire({ type: this.TEST_CASE_COMPLETE_EVENT, testCase: node.testObject, results: node.results});
1201                     }      
1202                 } 
1203             },                
1204             
1205             //-------------------------------------------------------------------------
1206             // Navigation Methods
1207             //-------------------------------------------------------------------------
1208             
1209             /**
1210              * Retrieves the next node in the test tree.
1211              * @return {TestNode} The next node in the test tree or null if the end is reached.
1212              * @private
1213              * @static
1214              * @method _next
1215              */
1216             _next : function () {
1217             
1218                 if (this._cur === null){
1219                     this._cur = this._root;
1220                 } else if (this._cur.firstChild) {
1221                     this._cur = this._cur.firstChild;
1222                 } else if (this._cur.next) {
1223                     this._cur = this._cur.next;            
1224                 } else {
1225                     while (this._cur && !this._cur.next && this._cur !== this._root){
1226                         this._handleTestObjectComplete(this._cur);
1227                         this._cur = this._cur.parent;
1228                     }
1229                     
1230                     this._handleTestObjectComplete(this._cur);               
1231                         
1232                     if (this._cur == this._root){
1233                         this._cur.results.type = "report";
1234                         this._cur.results.timestamp = (new Date()).toLocaleString();
1235                         this._cur.results.duration = (new Date()) - this._cur._start;   
1236                         this._lastResults = this._cur.results;
1237                         this._running = false;                         
1238                         this.fire({ type: this.COMPLETE_EVENT, results: this._lastResults});
1239                         this._cur = null;
1240                     } else if (this._cur) {
1241                         this._cur = this._cur.next;                
1242                     }
1243                 }
1244             
1245                 return this._cur;
1246             },
1247             
1248             /**
1249              * Executes a non-test method (init, setUp, tearDown, destroy)
1250              * and traps an errors. If an error occurs, an error event is
1251              * fired.
1252              * @param {Object} node The test node in the testing tree.
1253              * @param {String} methodName The name of the method to execute.
1254              * @param {Boolean} allowAsync Determines if the method can be called asynchronously.
1255              * @return {Boolean} True if an async method was called, false if not.
1256              * @method _execNonTestMethod
1257              * @private
1258              */
1259             _execNonTestMethod: function(node, methodName, allowAsync){
1260                 var testObject = node.testObject,
1261                     event = { type: this.ERROR_EVENT };
1262                 try {
1263                     if (allowAsync && testObject["async:" + methodName]){
1264                         testObject["async:" + methodName](this._context);
1265                         return true;
1266                     } else {
1267                         testObject[methodName](this._context);
1268                     }
1269                 } catch (ex){
1270                     node.results.errors++;
1271                     event.error = ex;
1272                     event.methodName = methodName;
1273                     if (testObject instanceof YUITest.TestCase){
1274                         event.testCase = testObject;
1275                     } else {
1276                         event.testSuite = testSuite;
1277                     }
1278                     
1279                     this.fire(event);
1280                 }  
1282                 return false;
1283             },
1284             
1285             /**
1286              * Runs a test case or test suite, returning the results.
1287              * @param {Test.TestCase|YUITest.TestSuite} testObject The test case or test suite to run.
1288              * @return {Object} Results of the execution with properties passed, failed, and total.
1289              * @private
1290              * @method _run
1291              * @static
1292              */
1293             _run : function () {
1294             
1295                 //flag to indicate if the TestRunner should wait before continuing
1296                 var shouldWait = false;
1297                 
1298                 //get the next test node
1299                 var node = this._next();
1300                 
1301                 if (node !== null) {
1302                 
1303                     //set flag to say the testrunner is running
1304                     this._running = true;
1305                     
1306                     //eliminate last results
1307                     this._lastResult = null;                  
1308                 
1309                     var testObject = node.testObject;
1310                     
1311                     //figure out what to do
1312                     if (typeof testObject == "object" && testObject !== null){
1313                         if (testObject instanceof YUITest.TestSuite){
1314                             this.fire({ type: this.TEST_SUITE_BEGIN_EVENT, testSuite: testObject });
1315                             node._start = new Date();
1316                             this._execNonTestMethod(node, "setUp" ,false);
1317                         } else if (testObject instanceof YUITest.TestCase){
1318                             this.fire({ type: this.TEST_CASE_BEGIN_EVENT, testCase: testObject });
1319                             node._start = new Date();
1320                             
1321                             //regular or async init
1322                             /*try {
1323                                 if (testObject["async:init"]){
1324                                     testObject["async:init"](this._context);
1325                                     return;
1326                                 } else {
1327                                     testObject.init(this._context);
1328                                 }
1329                             } catch (ex){
1330                                 node.results.errors++;
1331                                 this.fire({ type: this.ERROR_EVENT, error: ex, testCase: testObject, methodName: "init" });
1332                             }*/
1333                             if(this._execNonTestMethod(node, "init", true)){
1334                                 return;
1335                             }
1336                         }
1337                         
1338                         //some environments don't support setTimeout
1339                         if (typeof setTimeout != "undefined"){                    
1340                             setTimeout(function(){
1341                                 YUITest.TestRunner._run();
1342                             }, 0);
1343                         } else {
1344                             this._run();
1345                         }
1346                     } else {
1347                         this._runTest(node);
1348                     }
1349     
1350                 }
1351             },
1352             
1353             _resumeTest : function (segment) {
1354             
1355                 //get relevant information
1356                 var node = this._cur;                
1357                 
1358                 //we know there's no more waiting now
1359                 this._waiting = false;
1360                 
1361                 //if there's no node, it probably means a wait() was called after resume()
1362                 if (!node){
1363                     //TODO: Handle in some way?
1364                     //console.log("wait() called after resume()");
1365                     //this.fire("error", { testCase: "(unknown)", test: "(unknown)", error: new Error("wait() called after resume()")} );
1366                     return;
1367                 }
1368                 
1369                 var testName = node.testObject;
1370                 var testCase = node.parent.testObject;
1371             
1372                 //cancel other waits if available
1373                 if (testCase.__yui_wait){
1374                     clearTimeout(testCase.__yui_wait);
1375                     delete testCase.__yui_wait;
1376                 }
1378                 //get the "should" test cases
1379                 var shouldFail = testName.indexOf("fail:") === 0 ||
1380                                     (testCase._should.fail || {})[testName];
1381                 var shouldError = (testCase._should.error || {})[testName];
1382                 
1383                 //variable to hold whether or not the test failed
1384                 var failed = false;
1385                 var error = null;
1386                     
1387                 //try the test
1388                 try {
1389                 
1390                     //run the test
1391                     segment.call(testCase, this._context);                    
1392                 
1393                     //if the test hasn't already failed and doesn't have any asserts...
1394                     if(YUITest.Assert._getCount() == 0 && !this._ignoreEmpty){
1395                         throw new YUITest.AssertionError("Test has no asserts.");
1396                     }                                                        
1397                     //if it should fail, and it got here, then it's a fail because it didn't
1398                      else if (shouldFail){
1399                         error = new YUITest.ShouldFail();
1400                         failed = true;
1401                     } else if (shouldError){
1402                         error = new YUITest.ShouldError();
1403                         failed = true;
1404                     }
1405                                
1406                 } catch (thrown){
1408                     //cancel any pending waits, the test already failed
1409                     if (testCase.__yui_wait){
1410                         clearTimeout(testCase.__yui_wait);
1411                         delete testCase.__yui_wait;
1412                     }                    
1413                 
1414                     //figure out what type of error it was
1415                     if (thrown instanceof YUITest.AssertionError) {
1416                         if (!shouldFail){
1417                             error = thrown;
1418                             failed = true;
1419                         }
1420                     } else if (thrown instanceof YUITest.Wait){
1421                     
1422                         if (typeof thrown.segment == "function"){
1423                             if (typeof thrown.delay == "number"){
1424                             
1425                                 //some environments don't support setTimeout
1426                                 if (typeof setTimeout != "undefined"){
1427                                     testCase.__yui_wait = setTimeout(function(){
1428                                         YUITest.TestRunner._resumeTest(thrown.segment);
1429                                     }, thrown.delay);
1430                                     this._waiting = true;
1431                                 } else {
1432                                     throw new Error("Asynchronous tests not supported in this environment.");
1433                                 }
1434                             }
1435                         }
1436                         
1437                         return;
1438                     
1439                     } else {
1440                         //first check to see if it should error
1441                         if (!shouldError) {                        
1442                             error = new YUITest.UnexpectedError(thrown);
1443                             failed = true;
1444                         } else {
1445                             //check to see what type of data we have
1446                             if (typeof shouldError == "string"){
1447                                 
1448                                 //if it's a string, check the error message
1449                                 if (thrown.message != shouldError){
1450                                     error = new YUITest.UnexpectedError(thrown);
1451                                     failed = true;                                    
1452                                 }
1453                             } else if (typeof shouldError == "function"){
1454                             
1455                                 //if it's a function, see if the error is an instance of it
1456                                 if (!(thrown instanceof shouldError)){
1457                                     error = new YUITest.UnexpectedError(thrown);
1458                                     failed = true;
1459                                 }
1460                             
1461                             } else if (typeof shouldError == "object" && shouldError !== null){
1462                             
1463                                 //if it's an object, check the instance and message
1464                                 if (!(thrown instanceof shouldError.constructor) || 
1465                                         thrown.message != shouldError.message){
1466                                     error = new YUITest.UnexpectedError(thrown);
1467                                     failed = true;                                    
1468                                 }
1469                             
1470                             }
1471                         
1472                         }
1473                     }
1474                     
1475                 }
1476                 
1477                 //fire appropriate event
1478                 if (failed) {
1479                     this.fire({ type: this.TEST_FAIL_EVENT, testCase: testCase, testName: testName, error: error });
1480                 } else {
1481                     this.fire({ type: this.TEST_PASS_EVENT, testCase: testCase, testName: testName });
1482                 }
1483                 
1484                 //run the tear down
1485                 this._execNonTestMethod(node.parent, "tearDown", false);
1486                 
1487                 //reset the assert count
1488                 YUITest.Assert._reset();
1489                 
1490                 //calculate duration
1491                 var duration = (new Date()) - node._start;
1492                 
1493                 //update results
1494                 node.parent.results[testName] = { 
1495                     result: failed ? "fail" : "pass",
1496                     message: error ? error.getMessage() : "Test passed",
1497                     type: "test",
1498                     name: testName,
1499                     duration: duration
1500                 };
1501                 
1502                 if (failed){
1503                     node.parent.results.failed++;
1504                 } else {
1505                     node.parent.results.passed++;
1506                 }
1507                 node.parent.results.total++;
1508     
1509                 //set timeout not supported in all environments
1510                 if (typeof setTimeout != "undefined"){
1511                     setTimeout(function(){
1512                         YUITest.TestRunner._run();
1513                     }, 0);
1514                 } else {
1515                     this._run();
1516                 }
1517             
1518             },
1519             
1520             /**
1521              * Handles an error as if it occurred within the currently executing
1522              * test. This is for mock methods that may be called asynchronously
1523              * and therefore out of the scope of the TestRunner. Previously, this
1524              * error would bubble up to the browser. Now, this method is used
1525              * to tell TestRunner about the error. This should never be called
1526              * by anyplace other than the Mock object.
1527              * @param {Error} error The error object.
1528              * @return {Void}
1529              * @method _handleError
1530              * @private
1531              * @static
1532              */
1533             _handleError: function(error){
1534             
1535                 if (this._waiting){
1536                     this._resumeTest(function(){
1537                         throw error;
1538                     });
1539                 } else {
1540                     throw error;
1541                 }           
1542             
1543             },
1544                     
1545             /**
1546              * Runs a single test based on the data provided in the node.
1547              * @param {TestNode} node The TestNode representing the test to run.
1548              * @return {Void}
1549              * @static
1550              * @private
1551              * @name _runTest
1552              */
1553             _runTest : function (node) {
1554             
1555                 //get relevant information
1556                 var testName = node.testObject,
1557                     testCase = node.parent.testObject,
1558                     test = testCase[testName],
1559                 
1560                     //get the "should" test cases
1561                     shouldIgnore = testName.indexOf("ignore:") === 0 ||
1562                                     !inGroups(testCase.groups, this._groups) ||
1563                                     (testCase._should.ignore || {})[testName];   //deprecated
1564                 
1565                 //figure out if the test should be ignored or not
1566                 if (shouldIgnore){
1567                 
1568                     //update results
1569                     node.parent.results[testName] = { 
1570                         result: "ignore",
1571                         message: "Test ignored",
1572                         type: "test",
1573                         name: testName.indexOf("ignore:") === 0 ? testName.substring(7) : testName
1574                     };
1575                     
1576                     node.parent.results.ignored++;
1577                     node.parent.results.total++;
1578                 
1579                     this.fire({ type: this.TEST_IGNORE_EVENT,  testCase: testCase, testName: testName });
1580                     
1581                     //some environments don't support setTimeout
1582                     if (typeof setTimeout != "undefined"){                    
1583                         setTimeout(function(){
1584                             YUITest.TestRunner._run();
1585                         }, 0);              
1586                     } else {
1587                         this._run();
1588                     }
1589     
1590                 } else {
1591                 
1592                     //mark the start time
1593                     node._start = new Date();
1594                 
1595                     //run the setup
1596                     this._execNonTestMethod(node.parent, "setUp", false);
1597                     
1598                     //now call the body of the test
1599                     this._resumeTest(test);                
1600                 }
1601     
1602             },            
1604             //-------------------------------------------------------------------------
1605             // Misc Methods
1606             //-------------------------------------------------------------------------   
1608             /**
1609              * Retrieves the name of the current result set.
1610              * @return {String} The name of the result set.
1611              * @method getName
1612              */
1613             getName: function(){
1614                 return this.masterSuite.name;
1615             },         
1617             /**
1618              * The name assigned to the master suite of the TestRunner. This is the name
1619              * that is output as the root's name when results are retrieved.
1620              * @param {String} name The name of the result set.
1621              * @return {Void}
1622              * @method setName
1623              */
1624             setName: function(name){
1625                 this.masterSuite.name = name;
1626             },            
1627             
1628             //-------------------------------------------------------------------------
1629             // Public Methods
1630             //-------------------------------------------------------------------------   
1631         
1632             /**
1633              * Adds a test suite or test case to the list of test objects to run.
1634              * @param testObject Either a TestCase or a TestSuite that should be run.
1635              * @return {Void}
1636              * @method add
1637              * @static
1638              */
1639             add : function (testObject) {
1640                 this.masterSuite.add(testObject);
1641                 return this;
1642             },
1643             
1644             /**
1645              * Removes all test objects from the runner.
1646              * @return {Void}
1647              * @method clear
1648              * @static
1649              */
1650             clear : function () {
1651                 this.masterSuite = new YUITest.TestSuite("yuitests" + (new Date()).getTime());
1652             },
1653             
1654             /**
1655              * Indicates if the TestRunner is waiting for a test to resume
1656              * @return {Boolean} True if the TestRunner is waiting, false if not.
1657              * @method isWaiting
1658              * @static
1659              */
1660             isWaiting: function() {
1661                 return this._waiting;
1662             },
1663             
1664             /**
1665              * Indicates that the TestRunner is busy running tests and therefore can't
1666              * be stopped and results cannot be gathered.
1667              * @return {Boolean} True if the TestRunner is running, false if not.
1668              * @method isRunning
1669              */
1670             isRunning: function(){
1671                 return this._running;
1672             },
1673             
1674             /**
1675              * Returns the last complete results set from the TestRunner. Null is returned
1676              * if the TestRunner is running or no tests have been run.
1677              * @param {Function} format (Optional) A test format to return the results in.
1678              * @return {Object|String} Either the results object or, if a test format is 
1679              *      passed as the argument, a string representing the results in a specific
1680              *      format.
1681              * @method getResults
1682              */
1683             getResults: function(format){
1684                 if (!this._running && this._lastResults){
1685                     if (typeof format == "function"){
1686                         return format(this._lastResults);                    
1687                     } else {
1688                         return this._lastResults;
1689                     }
1690                 } else {
1691                     return null;
1692                 }
1693             },            
1694             
1695             /**
1696              * Returns the coverage report for the files that have been executed.
1697              * This returns only coverage information for files that have been
1698              * instrumented using YUI Test Coverage and only those that were run
1699              * in the same pass.
1700              * @param {Function} format (Optional) A coverage format to return results in.
1701              * @return {Object|String} Either the coverage object or, if a coverage
1702              *      format is specified, a string representing the results in that format.
1703              * @method getCoverage
1704              */
1705             getCoverage: function(format){
1706                 if (!this._running && typeof _yuitest_coverage == "object"){
1707                     if (typeof format == "function"){
1708                         return format(_yuitest_coverage);                    
1709                     } else {
1710                         return _yuitest_coverage;
1711                     }
1712                 } else {
1713                     return null;
1714                 }            
1715             },
1716             
1717             /**
1718              * Used to continue processing when a method marked with
1719              * "async:" is executed. This should not be used in test
1720              * methods, only in init(). Each argument is a string, and
1721              * when the returned function is executed, the arguments
1722              * are assigned to the context data object using the string
1723              * as the key name (value is the argument itself).
1724              * @private
1725              * @return {Function} A callback function.
1726              */
1727             callback: function(){
1728                 var names   = arguments,
1729                     data    = this._context,
1730                     that    = this;
1731                     
1732                 return function(){
1733                     for (var i=0; i < arguments.length; i++){
1734                         data[names[i]] = arguments[i];
1735                     }
1736                     that._run();
1737                 };
1738             },
1739             
1740             /**
1741              * Resumes the TestRunner after wait() was called.
1742              * @param {Function} segment The function to run as the rest
1743              *      of the haulted test.
1744              * @return {Void}
1745              * @method resume
1746              * @static
1747              */
1748             resume : function (segment) {
1749                 if (this._waiting){
1750                     this._resumeTest(segment || function(){});
1751                 } else {
1752                     throw new Error("resume() called without wait().");
1753                 }
1754             },
1755         
1756             /**
1757              * Runs the test suite.
1758              * @param {Object|Boolean} options (Optional) Options for the runner:
1759              *      <code>oldMode</code> indicates the TestRunner should work in the YUI <= 2.8 way
1760              *      of internally managing test suites. <code>groups</code> is an array
1761              *      of test groups indicating which tests to run.
1762              * @return {Void}
1763              * @method run
1764              * @static
1765              */
1766             run : function (options) {
1768                 options = options || {};
1769                 
1770                 //pointer to runner to avoid scope issues 
1771                 var runner  = YUITest.TestRunner,
1772                     oldMode = options.oldMode;
1773                 
1774                 
1775                 //if there's only one suite on the masterSuite, move it up
1776                 if (!oldMode && this.masterSuite.items.length == 1 && this.masterSuite.items[0] instanceof YUITest.TestSuite){
1777                     this.masterSuite = this.masterSuite.items[0];
1778                 }                
1779                 
1780                 //determine if there are any groups to filter on
1781                 runner._groups = (options.groups instanceof Array) ? "," + options.groups.join(",") + "," : "";
1782                 
1783                 //initialize the runner
1784                 runner._buildTestTree();
1785                 runner._context = {};
1786                 runner._root._start = new Date();
1787                 
1788                 //fire the begin event
1789                 runner.fire(runner.BEGIN_EVENT);
1790            
1791                 //begin the testing
1792                 runner._run();
1793             }    
1794         });
1795         
1796         return new TestRunner();
1797         
1798     }();
1801  * The ArrayAssert object provides functions to test JavaScript array objects
1802  * for a variety of cases.
1803  * @namespace Test
1804  * @class ArrayAssert
1805  * @static
1806  */
1808 YUITest.ArrayAssert = {
1810     //=========================================================================
1811     // Private methods
1812     //=========================================================================
1813     
1814     /**
1815      * Simple indexOf() implementation for an array. Defers to native
1816      * if available.
1817      * @param {Array} haystack The array to search.
1818      * @param {Variant} needle The value to locate.
1819      * @return {int} The index of the needle if found or -1 if not.
1820      * @method _indexOf
1821      * @private
1822      */
1823     _indexOf: function(haystack, needle){
1824         if (haystack.indexOf){
1825             return haystack.indexOf(needle);
1826         } else {
1827             for (var i=0; i < haystack.length; i++){
1828                 if (haystack[i] === needle){
1829                     return i;
1830                 }
1831             }
1832             return -1;
1833         }
1834     },
1835     
1836     /**
1837      * Simple some() implementation for an array. Defers to native
1838      * if available.
1839      * @param {Array} haystack The array to search.
1840      * @param {Function} matcher The function to run on each value.
1841      * @return {Boolean} True if any value, when run through the matcher,
1842      *      returns true.
1843      * @method _some
1844      * @private
1845      */
1846     _some: function(haystack, matcher){
1847         if (haystack.some){
1848             return haystack.some(matcher);
1849         } else {
1850             for (var i=0; i < haystack.length; i++){
1851                 if (matcher(haystack[i])){
1852                     return true;
1853                 }
1854             }
1855             return false;
1856         }
1857     },    
1859     /**
1860      * Asserts that a value is present in an array. This uses the triple equals 
1861      * sign so no type cohersion may occur.
1862      * @param {Object} needle The value that is expected in the array.
1863      * @param {Array} haystack An array of values.
1864      * @param {String} message (Optional) The message to display if the assertion fails.
1865      * @method contains
1866      * @static
1867      */
1868     contains : function (needle, haystack, 
1869                            message) {
1870         
1871         YUITest.Assert._increment();               
1873         if (this._indexOf(haystack, needle) == -1){
1874             YUITest.Assert.fail(YUITest.Assert._formatMessage(message, "Value " + needle + " (" + (typeof needle) + ") not found in array [" + haystack + "]."));
1875         }
1876     },
1878     /**
1879      * Asserts that a set of values are present in an array. This uses the triple equals 
1880      * sign so no type cohersion may occur. For this assertion to pass, all values must
1881      * be found.
1882      * @param {Object[]} needles An array of values that are expected in the array.
1883      * @param {Array} haystack An array of values to check.
1884      * @param {String} message (Optional) The message to display if the assertion fails.
1885      * @method containsItems
1886      * @static
1887      */
1888     containsItems : function (needles, haystack, 
1889                            message) {
1890         YUITest.Assert._increment();               
1892         //begin checking values
1893         for (var i=0; i < needles.length; i++){
1894             if (this._indexOf(haystack, needles[i]) == -1){
1895                 YUITest.Assert.fail(YUITest.Assert._formatMessage(message, "Value " + needles[i] + " (" + (typeof needles[i]) + ") not found in array [" + haystack + "]."));
1896             }
1897         }
1898     },
1900     /**
1901      * Asserts that a value matching some condition is present in an array. This uses
1902      * a function to determine a match.
1903      * @param {Function} matcher A function that returns true if the items matches or false if not.
1904      * @param {Array} haystack An array of values.
1905      * @param {String} message (Optional) The message to display if the assertion fails.
1906      * @method containsMatch
1907      * @static
1908      */
1909     containsMatch : function (matcher, haystack, 
1910                            message) {
1911         
1912         YUITest.Assert._increment();               
1913         //check for valid matcher
1914         if (typeof matcher != "function"){
1915             throw new TypeError("ArrayAssert.containsMatch(): First argument must be a function.");
1916         }
1917         
1918         if (!this._some(haystack, matcher)){
1919             YUITest.Assert.fail(YUITest.Assert._formatMessage(message, "No match found in array [" + haystack + "]."));
1920         }
1921     },
1923     /**
1924      * Asserts that a value is not present in an array. This uses the triple equals 
1925      * Asserts that a value is not present in an array. This uses the triple equals 
1926      * sign so no type cohersion may occur.
1927      * @param {Object} needle The value that is expected in the array.
1928      * @param {Array} haystack An array of values.
1929      * @param {String} message (Optional) The message to display if the assertion fails.
1930      * @method doesNotContain
1931      * @static
1932      */
1933     doesNotContain : function (needle, haystack, 
1934                            message) {
1935         
1936         YUITest.Assert._increment();               
1938         if (this._indexOf(haystack, needle) > -1){
1939             YUITest.Assert.fail(YUITest.Assert._formatMessage(message, "Value found in array [" + haystack + "]."));
1940         }
1941     },
1943     /**
1944      * Asserts that a set of values are not present in an array. This uses the triple equals 
1945      * sign so no type cohersion may occur. For this assertion to pass, all values must
1946      * not be found.
1947      * @param {Object[]} needles An array of values that are not expected in the array.
1948      * @param {Array} haystack An array of values to check.
1949      * @param {String} message (Optional) The message to display if the assertion fails.
1950      * @method doesNotContainItems
1951      * @static
1952      */
1953     doesNotContainItems : function (needles, haystack, 
1954                            message) {
1956         YUITest.Assert._increment();               
1958         for (var i=0; i < needles.length; i++){
1959             if (this._indexOf(haystack, needles[i]) > -1){
1960                 YUITest.Assert.fail(YUITest.Assert._formatMessage(message, "Value found in array [" + haystack + "]."));
1961             }
1962         }
1964     },
1965         
1966     /**
1967      * Asserts that no values matching a condition are present in an array. This uses
1968      * a function to determine a match.
1969      * @param {Function} matcher A function that returns true if the item matches or false if not.
1970      * @param {Array} haystack An array of values.
1971      * @param {String} message (Optional) The message to display if the assertion fails.
1972      * @method doesNotContainMatch
1973      * @static
1974      */
1975     doesNotContainMatch : function (matcher, haystack, 
1976                            message) {
1977         
1978         YUITest.Assert._increment();     
1979       
1980         //check for valid matcher
1981         if (typeof matcher != "function"){
1982             throw new TypeError("ArrayAssert.doesNotContainMatch(): First argument must be a function.");
1983         }
1984         
1985         if (this._some(haystack, matcher)){
1986             YUITest.Assert.fail(YUITest.Assert._formatMessage(message, "Value found in array [" + haystack + "]."));
1987         }
1988     },
1989         
1990     /**
1991      * Asserts that the given value is contained in an array at the specified index.
1992      * This uses the triple equals sign so no type cohersion will occur.
1993      * @param {Object} needle The value to look for.
1994      * @param {Array} haystack The array to search in.
1995      * @param {int} index The index at which the value should exist.
1996      * @param {String} message (Optional) The message to display if the assertion fails.
1997      * @method indexOf
1998      * @static
1999      */
2000     indexOf : function (needle, haystack, index, message) {
2001     
2002         YUITest.Assert._increment();     
2004         //try to find the value in the array
2005         for (var i=0; i < haystack.length; i++){
2006             if (haystack[i] === needle){
2007                 if (index != i){
2008                     YUITest.Assert.fail(YUITest.Assert._formatMessage(message, "Value exists at index " + i + " but should be at index " + index + "."));                    
2009                 }
2010                 return;
2011             }
2012         }
2013         
2014         //if it makes it here, it wasn't found at all
2015         YUITest.Assert.fail(YUITest.Assert._formatMessage(message, "Value doesn't exist in array [" + haystack + "]."));
2016     },
2017         
2018     /**
2019      * Asserts that the values in an array are equal, and in the same position,
2020      * as values in another array. This uses the double equals sign
2021      * so type cohersion may occur. Note that the array objects themselves
2022      * need not be the same for this test to pass.
2023      * @param {Array} expected An array of the expected values.
2024      * @param {Array} actual Any array of the actual values.
2025      * @param {String} message (Optional) The message to display if the assertion fails.
2026      * @method itemsAreEqual
2027      * @static
2028      */
2029     itemsAreEqual : function (expected, actual, 
2030                            message) {
2031         
2032         YUITest.Assert._increment();     
2033         
2034         //first make sure they're array-like (this can probably be improved)
2035         if (typeof expected != "object" || typeof actual != "object"){
2036             YUITest.Assert.fail(YUITest.Assert._formatMessage(message, "Value should be an array."));
2037         }
2038         
2039         //next check array length
2040         if (expected.length != actual.length){
2041             YUITest.Assert.fail(YUITest.Assert._formatMessage(message, "Array should have a length of " + expected.length + " but has a length of " + actual.length + "."));
2042         }
2043        
2044         //begin checking values
2045         for (var i=0; i < expected.length; i++){
2046             if (expected[i] != actual[i]){
2047                 throw new YUITest.ComparisonFailure(YUITest.Assert._formatMessage(message, "Values in position " + i + " are not equal."), expected[i], actual[i]);
2048             }
2049         }
2050     },
2051     
2052     /**
2053      * Asserts that the values in an array are equivalent, and in the same position,
2054      * as values in another array. This uses a function to determine if the values
2055      * are equivalent. Note that the array objects themselves
2056      * need not be the same for this test to pass.
2057      * @param {Array} expected An array of the expected values.
2058      * @param {Array} actual Any array of the actual values.
2059      * @param {Function} comparator A function that returns true if the values are equivalent
2060      *      or false if not.
2061      * @param {String} message (Optional) The message to display if the assertion fails.
2062      * @return {Void}
2063      * @method itemsAreEquivalent
2064      * @static
2065      */
2066     itemsAreEquivalent : function (expected, actual, 
2067                            comparator, message) {
2068         
2069         YUITest.Assert._increment();     
2071         //make sure the comparator is valid
2072         if (typeof comparator != "function"){
2073             throw new TypeError("ArrayAssert.itemsAreEquivalent(): Third argument must be a function.");
2074         }
2075         
2076         //first check array length
2077         if (expected.length != actual.length){
2078             YUITest.Assert.fail(YUITest.Assert._formatMessage(message, "Array should have a length of " + expected.length + " but has a length of " + actual.length));
2079         }
2080         
2081         //begin checking values
2082         for (var i=0; i < expected.length; i++){
2083             if (!comparator(expected[i], actual[i])){
2084                 throw new YUITest.ComparisonFailure(YUITest.Assert._formatMessage(message, "Values in position " + i + " are not equivalent."), expected[i], actual[i]);
2085             }
2086         }
2087     },
2088     
2089     /**
2090      * Asserts that an array is empty.
2091      * @param {Array} actual The array to test.
2092      * @param {String} message (Optional) The message to display if the assertion fails.
2093      * @method isEmpty
2094      * @static
2095      */
2096     isEmpty : function (actual, message) {        
2097         YUITest.Assert._increment();     
2098         if (actual.length > 0){
2099             YUITest.Assert.fail(YUITest.Assert._formatMessage(message, "Array should be empty."));
2100         }
2101     },    
2102     
2103     /**
2104      * Asserts that an array is not empty.
2105      * @param {Array} actual The array to test.
2106      * @param {String} message (Optional) The message to display if the assertion fails.
2107      * @method isNotEmpty
2108      * @static
2109      */
2110     isNotEmpty : function (actual, message) {        
2111         YUITest.Assert._increment();     
2112         if (actual.length === 0){
2113             YUITest.Assert.fail(YUITest.Assert._formatMessage(message, "Array should not be empty."));
2114         }
2115     },    
2116     
2117     /**
2118      * Asserts that the values in an array are the same, and in the same position,
2119      * as values in another array. This uses the triple equals sign
2120      * so no type cohersion will occur. Note that the array objects themselves
2121      * need not be the same for this test to pass.
2122      * @param {Array} expected An array of the expected values.
2123      * @param {Array} actual Any array of the actual values.
2124      * @param {String} message (Optional) The message to display if the assertion fails.
2125      * @method itemsAreSame
2126      * @static
2127      */
2128     itemsAreSame : function (expected, actual, 
2129                           message) {
2130         
2131         YUITest.Assert._increment();     
2133         //first check array length
2134         if (expected.length != actual.length){
2135             YUITest.Assert.fail(YUITest.Assert._formatMessage(message, "Array should have a length of " + expected.length + " but has a length of " + actual.length));
2136         }
2137                     
2138         //begin checking values
2139         for (var i=0; i < expected.length; i++){
2140             if (expected[i] !== actual[i]){
2141                 throw new YUITest.ComparisonFailure(YUITest.Assert._formatMessage(message, "Values in position " + i + " are not the same."), expected[i], actual[i]);
2142             }
2143         }
2144     },
2145     
2146     /**
2147      * Asserts that the given value is contained in an array at the specified index,
2148      * starting from the back of the array.
2149      * This uses the triple equals sign so no type cohersion will occur.
2150      * @param {Object} needle The value to look for.
2151      * @param {Array} haystack The array to search in.
2152      * @param {int} index The index at which the value should exist.
2153      * @param {String} message (Optional) The message to display if the assertion fails.
2154      * @method lastIndexOf
2155      * @static
2156      */
2157     lastIndexOf : function (needle, haystack, index, message) {
2158     
2159         //try to find the value in the array
2160         for (var i=haystack.length; i >= 0; i--){
2161             if (haystack[i] === needle){
2162                 if (index != i){
2163                     YUITest.Assert.fail(YUITest.Assert._formatMessage(message, "Value exists at index " + i + " but should be at index " + index + "."));                    
2164                 }
2165                 return;
2166             }
2167         }
2168         
2169         //if it makes it here, it wasn't found at all
2170         YUITest.Assert.fail(YUITest.Assert._formatMessage(message, "Value doesn't exist in array."));        
2171     }
2172     
2174   
2176  * The Assert object provides functions to test JavaScript values against
2177  * known and expected results. Whenever a comparison (assertion) fails,
2178  * an error is thrown.
2179  * @namespace Test
2180  * @class Assert
2181  * @static
2182  */
2183 YUITest.Assert = {
2185     /**
2186      * The number of assertions performed.
2187      * @property _asserts
2188      * @type int
2189      * @private
2190      */
2191     _asserts: 0,
2193     //-------------------------------------------------------------------------
2194     // Helper Methods
2195     //-------------------------------------------------------------------------
2196     
2197     /**
2198      * Formats a message so that it can contain the original assertion message
2199      * in addition to the custom message.
2200      * @param {String} customMessage The message passed in by the developer.
2201      * @param {String} defaultMessage The message created by the error by default.
2202      * @return {String} The final error message, containing either or both.
2203      * @protected
2204      * @static
2205      * @method _formatMessage
2206      */
2207     _formatMessage : function (customMessage, defaultMessage) {
2208         if (typeof customMessage == "string" && customMessage.length > 0){
2209             return customMessage.replace("{message}", defaultMessage);
2210         } else {
2211             return defaultMessage;
2212         }        
2213     },
2214     
2215     /**
2216      * Returns the number of assertions that have been performed.
2217      * @method _getCount
2218      * @protected
2219      * @static
2220      */
2221     _getCount: function(){
2222         return this._asserts;
2223     },
2224     
2225     /**
2226      * Increments the number of assertions that have been performed.
2227      * @method _increment
2228      * @protected
2229      * @static
2230      */
2231     _increment: function(){
2232         this._asserts++;
2233     },
2234     
2235     /**
2236      * Resets the number of assertions that have been performed to 0.
2237      * @method _reset
2238      * @protected
2239      * @static
2240      */
2241     _reset: function(){
2242         this._asserts = 0;
2243     },
2244     
2245     //-------------------------------------------------------------------------
2246     // Generic Assertion Methods
2247     //-------------------------------------------------------------------------
2248     
2249     /** 
2250      * Forces an assertion error to occur.
2251      * @param {String} message (Optional) The message to display with the failure.
2252      * @method fail
2253      * @static
2254      */
2255     fail : function (message) {
2256         throw new YUITest.AssertionError(YUITest.Assert._formatMessage(message, "Test force-failed."));
2257     },       
2258     
2259     /** 
2260      * A marker that the test should pass.
2261      * @method pass
2262      * @static
2263      */
2264     pass : function (message) {
2265         YUITest.Assert._increment();
2266     },       
2267     
2268     //-------------------------------------------------------------------------
2269     // Equality Assertion Methods
2270     //-------------------------------------------------------------------------    
2271     
2272     /**
2273      * Asserts that a value is equal to another. This uses the double equals sign
2274      * so type cohersion may occur.
2275      * @param {Object} expected The expected value.
2276      * @param {Object} actual The actual value to test.
2277      * @param {String} message (Optional) The message to display if the assertion fails.
2278      * @method areEqual
2279      * @static
2280      */
2281     areEqual : function (expected, actual, message) {
2282         YUITest.Assert._increment();
2283         if (expected != actual) {
2284             throw new YUITest.ComparisonFailure(YUITest.Assert._formatMessage(message, "Values should be equal."), expected, actual);
2285         }
2286     },
2287     
2288     /**
2289      * Asserts that a value is not equal to another. This uses the double equals sign
2290      * so type cohersion may occur.
2291      * @param {Object} unexpected The unexpected value.
2292      * @param {Object} actual The actual value to test.
2293      * @param {String} message (Optional) The message to display if the assertion fails.
2294      * @method areNotEqual
2295      * @static
2296      */
2297     areNotEqual : function (unexpected, actual, 
2298                          message) {
2299         YUITest.Assert._increment();
2300         if (unexpected == actual) {
2301             throw new YUITest.UnexpectedValue(YUITest.Assert._formatMessage(message, "Values should not be equal."), unexpected);
2302         }
2303     },
2304     
2305     /**
2306      * Asserts that a value is not the same as another. This uses the triple equals sign
2307      * so no type cohersion may occur.
2308      * @param {Object} unexpected The unexpected value.
2309      * @param {Object} actual The actual value to test.
2310      * @param {String} message (Optional) The message to display if the assertion fails.
2311      * @method areNotSame
2312      * @static
2313      */
2314     areNotSame : function (unexpected, actual, message) {
2315         YUITest.Assert._increment();
2316         if (unexpected === actual) {
2317             throw new YUITest.UnexpectedValue(YUITest.Assert._formatMessage(message, "Values should not be the same."), unexpected);
2318         }
2319     },
2321     /**
2322      * Asserts that a value is the same as another. This uses the triple equals sign
2323      * so no type cohersion may occur.
2324      * @param {Object} expected The expected value.
2325      * @param {Object} actual The actual value to test.
2326      * @param {String} message (Optional) The message to display if the assertion fails.
2327      * @method areSame
2328      * @static
2329      */
2330     areSame : function (expected, actual, message) {
2331         YUITest.Assert._increment();
2332         if (expected !== actual) {
2333             throw new YUITest.ComparisonFailure(YUITest.Assert._formatMessage(message, "Values should be the same."), expected, actual);
2334         }
2335     },    
2336     
2337     //-------------------------------------------------------------------------
2338     // Boolean Assertion Methods
2339     //-------------------------------------------------------------------------    
2340     
2341     /**
2342      * Asserts that a value is false. This uses the triple equals sign
2343      * so no type cohersion may occur.
2344      * @param {Object} actual The actual value to test.
2345      * @param {String} message (Optional) The message to display if the assertion fails.
2346      * @method isFalse
2347      * @static
2348      */
2349     isFalse : function (actual, message) {
2350         YUITest.Assert._increment();
2351         if (false !== actual) {
2352             throw new YUITest.ComparisonFailure(YUITest.Assert._formatMessage(message, "Value should be false."), false, actual);
2353         }
2354     },
2355     
2356     /**
2357      * Asserts that a value is true. This uses the triple equals sign
2358      * so no type cohersion may occur.
2359      * @param {Object} actual The actual value to test.
2360      * @param {String} message (Optional) The message to display if the assertion fails.
2361      * @method isTrue
2362      * @static
2363      */
2364     isTrue : function (actual, message) {
2365         YUITest.Assert._increment();
2366         if (true !== actual) {
2367             throw new YUITest.ComparisonFailure(YUITest.Assert._formatMessage(message, "Value should be true."), true, actual);
2368         }
2370     },
2371     
2372     //-------------------------------------------------------------------------
2373     // Special Value Assertion Methods
2374     //-------------------------------------------------------------------------    
2375     
2376     /**
2377      * Asserts that a value is not a number.
2378      * @param {Object} actual The value to test.
2379      * @param {String} message (Optional) The message to display if the assertion fails.
2380      * @method isNaN
2381      * @static
2382      */
2383     isNaN : function (actual, message){
2384         YUITest.Assert._increment();
2385         if (!isNaN(actual)){
2386             throw new YUITest.ComparisonFailure(YUITest.Assert._formatMessage(message, "Value should be NaN."), NaN, actual);
2387         }    
2388     },
2389     
2390     /**
2391      * Asserts that a value is not the special NaN value.
2392      * @param {Object} actual The value to test.
2393      * @param {String} message (Optional) The message to display if the assertion fails.
2394      * @method isNotNaN
2395      * @static
2396      */
2397     isNotNaN : function (actual, message){
2398         YUITest.Assert._increment();
2399         if (isNaN(actual)){
2400             throw new YUITest.UnexpectedValue(YUITest.Assert._formatMessage(message, "Values should not be NaN."), NaN);
2401         }    
2402     },
2403     
2404     /**
2405      * Asserts that a value is not null. This uses the triple equals sign
2406      * so no type cohersion may occur.
2407      * @param {Object} actual The actual value to test.
2408      * @param {String} message (Optional) The message to display if the assertion fails.
2409      * @method isNotNull
2410      * @static
2411      */
2412     isNotNull : function (actual, message) {
2413         YUITest.Assert._increment();
2414         if (actual === null) {
2415             throw new YUITest.UnexpectedValue(YUITest.Assert._formatMessage(message, "Values should not be null."), null);
2416         }
2417     },
2419     /**
2420      * Asserts that a value is not undefined. This uses the triple equals sign
2421      * so no type cohersion may occur.
2422      * @param {Object} actual The actual value to test.
2423      * @param {String} message (Optional) The message to display if the assertion fails.
2424      * @method isNotUndefined
2425      * @static
2426      */
2427     isNotUndefined : function (actual, message) {
2428         YUITest.Assert._increment();
2429         if (typeof actual == "undefined") {
2430             throw new YUITest.UnexpectedValue(YUITest.Assert._formatMessage(message, "Value should not be undefined."), undefined);
2431         }
2432     },
2434     /**
2435      * Asserts that a value is null. This uses the triple equals sign
2436      * so no type cohersion may occur.
2437      * @param {Object} actual The actual value to test.
2438      * @param {String} message (Optional) The message to display if the assertion fails.
2439      * @method isNull
2440      * @static
2441      */
2442     isNull : function (actual, message) {
2443         YUITest.Assert._increment();
2444         if (actual !== null) {
2445             throw new YUITest.ComparisonFailure(YUITest.Assert._formatMessage(message, "Value should be null."), null, actual);
2446         }
2447     },
2448         
2449     /**
2450      * Asserts that a value is undefined. This uses the triple equals sign
2451      * so no type cohersion may occur.
2452      * @param {Object} actual The actual value to test.
2453      * @param {String} message (Optional) The message to display if the assertion fails.
2454      * @method isUndefined
2455      * @static
2456      */
2457     isUndefined : function (actual, message) {
2458         YUITest.Assert._increment();
2459         if (typeof actual != "undefined") {
2460             throw new YUITest.ComparisonFailure(YUITest.Assert._formatMessage(message, "Value should be undefined."), undefined, actual);
2461         }
2462     },    
2463     
2464     //--------------------------------------------------------------------------
2465     // Instance Assertion Methods
2466     //--------------------------------------------------------------------------    
2467    
2468     /**
2469      * Asserts that a value is an array.
2470      * @param {Object} actual The value to test.
2471      * @param {String} message (Optional) The message to display if the assertion fails.
2472      * @method isArray
2473      * @static
2474      */
2475     isArray : function (actual, message) {
2476         YUITest.Assert._increment();
2477         var shouldFail = false;
2478         if (Array.isArray){
2479             shouldFail = !Array.isArray(actual);
2480         } else {
2481             shouldFail = Object.prototype.toString.call(actual) != "[object Array]";
2482         }
2483         if (shouldFail){
2484             throw new YUITest.UnexpectedValue(YUITest.Assert._formatMessage(message, "Value should be an array."), actual);
2485         }    
2486     },
2487    
2488     /**
2489      * Asserts that a value is a Boolean.
2490      * @param {Object} actual The value to test.
2491      * @param {String} message (Optional) The message to display if the assertion fails.
2492      * @method isBoolean
2493      * @static
2494      */
2495     isBoolean : function (actual, message) {
2496         YUITest.Assert._increment();
2497         if (typeof actual != "boolean"){
2498             throw new YUITest.UnexpectedValue(YUITest.Assert._formatMessage(message, "Value should be a Boolean."), actual);
2499         }    
2500     },
2501    
2502     /**
2503      * Asserts that a value is a function.
2504      * @param {Object} actual The value to test.
2505      * @param {String} message (Optional) The message to display if the assertion fails.
2506      * @method isFunction
2507      * @static
2508      */
2509     isFunction : function (actual, message) {
2510         YUITest.Assert._increment();
2511         if (!(actual instanceof Function)){
2512             throw new YUITest.UnexpectedValue(YUITest.Assert._formatMessage(message, "Value should be a function."), actual);
2513         }    
2514     },
2515    
2516     /**
2517      * Asserts that a value is an instance of a particular object. This may return
2518      * incorrect results when comparing objects from one frame to constructors in
2519      * another frame. For best results, don't use in a cross-frame manner.
2520      * @param {Function} expected The function that the object should be an instance of.
2521      * @param {Object} actual The object to test.
2522      * @param {String} message (Optional) The message to display if the assertion fails.
2523      * @method isInstanceOf
2524      * @static
2525      */
2526     isInstanceOf : function (expected, actual, message) {
2527         YUITest.Assert._increment();
2528         if (!(actual instanceof expected)){
2529             throw new YUITest.ComparisonFailure(YUITest.Assert._formatMessage(message, "Value isn't an instance of expected type."), expected, actual);
2530         }
2531     },
2532     
2533     /**
2534      * Asserts that a value is a number.
2535      * @param {Object} actual The value to test.
2536      * @param {String} message (Optional) The message to display if the assertion fails.
2537      * @method isNumber
2538      * @static
2539      */
2540     isNumber : function (actual, message) {
2541         YUITest.Assert._increment();
2542         if (typeof actual != "number"){
2543             throw new YUITest.UnexpectedValue(YUITest.Assert._formatMessage(message, "Value should be a number."), actual);
2544         }    
2545     },    
2546     
2547     /**
2548      * Asserts that a value is an object.
2549      * @param {Object} actual The value to test.
2550      * @param {String} message (Optional) The message to display if the assertion fails.
2551      * @method isObject
2552      * @static
2553      */
2554     isObject : function (actual, message) {
2555         YUITest.Assert._increment();
2556         if (!actual || (typeof actual != "object" && typeof actual != "function")){
2557             throw new YUITest.UnexpectedValue(YUITest.Assert._formatMessage(message, "Value should be an object."), actual);
2558         }
2559     },
2560     
2561     /**
2562      * Asserts that a value is a string.
2563      * @param {Object} actual The value to test.
2564      * @param {String} message (Optional) The message to display if the assertion fails.
2565      * @method isString
2566      * @static
2567      */
2568     isString : function (actual, message) {
2569         YUITest.Assert._increment();
2570         if (typeof actual != "string"){
2571             throw new YUITest.UnexpectedValue(YUITest.Assert._formatMessage(message, "Value should be a string."), actual);
2572         }
2573     },
2574     
2575     /**
2576      * Asserts that a value is of a particular type. 
2577      * @param {String} expectedType The expected type of the variable.
2578      * @param {Object} actualValue The actual value to test.
2579      * @param {String} message (Optional) The message to display if the assertion fails.
2580      * @method isTypeOf
2581      * @static
2582      */
2583     isTypeOf : function (expectedType, actualValue, message){
2584         YUITest.Assert._increment();
2585         if (typeof actualValue != expectedType){
2586             throw new YUITest.ComparisonFailure(YUITest.Assert._formatMessage(message, "Value should be of type " + expectedType + "."), expectedType, typeof actualValue);
2587         }
2588     },
2589     
2590     //--------------------------------------------------------------------------
2591     // Error Detection Methods
2592     //--------------------------------------------------------------------------    
2593    
2594     /**
2595      * Asserts that executing a particular method should throw an error of
2596      * a specific type. This is a replacement for _should.error.
2597      * @param {String|Function|Object} expectedError If a string, this
2598      *      is the error message that the error must have; if a function, this
2599      *      is the constructor that should have been used to create the thrown
2600      *      error; if an object, this is an instance of a particular error type
2601      *      with a specific error message (both must match).
2602      * @param {Function} method The method to execute that should throw the error.
2603      * @param {String} message (Optional) The message to display if the assertion
2604      *      fails.
2605      * @method throwsError
2606      * @return {void}
2607      * @static
2608      */
2609     throwsError: function(expectedError, method, message){
2610         YUITest.Assert._increment();
2611         var error = false;
2612     
2613         try {
2614             method();        
2615         } catch (thrown) {
2616             
2617             //check to see what type of data we have
2618             if (typeof expectedError == "string"){
2619                 
2620                 //if it's a string, check the error message
2621                 if (thrown.message != expectedError){
2622                     error = true;
2623                 }
2624             } else if (typeof expectedError == "function"){
2625             
2626                 //if it's a function, see if the error is an instance of it
2627                 if (!(thrown instanceof expectedError)){
2628                     error = true;
2629                 }
2630             
2631             } else if (typeof expectedError == "object" && expectedError !== null){
2632             
2633                 //if it's an object, check the instance and message
2634                 if (!(thrown instanceof expectedError.constructor) || 
2635                         thrown.message != expectedError.message){
2636                     error = true;
2637                 }
2638             
2639             } else { //if it gets here, the argument could be wrong
2640                 error = true;
2641             }
2642             
2643             if (error){
2644                 throw new YUITest.UnexpectedError(thrown);                    
2645             } else {
2646                 return;
2647             }
2648         }
2649         
2650         //if it reaches here, the error wasn't thrown, which is a bad thing
2651         throw new YUITest.AssertionError(YUITest.Assert._formatMessage(message, "Error should have been thrown."));
2652     }
2656  * Error is thrown whenever an assertion fails. It provides methods
2657  * to more easily get at error information and also provides a base class
2658  * from which more specific assertion errors can be derived.
2660  * @param {String} message The message to display when the error occurs.
2661  * @namespace Test
2662  * @class AssertionError
2663  * @constructor
2664  */ 
2665 YUITest.AssertionError = function (message){
2666     
2667     /**
2668      * Error message. Must be duplicated to ensure browser receives it.
2669      * @type String
2670      * @property message
2671      */
2672     this.message = message;
2673     
2674     /**
2675      * The name of the error that occurred.
2676      * @type String
2677      * @property name
2678      */
2679     this.name = "Assert Error";
2682 YUITest.AssertionError.prototype = {
2684     //restore constructor
2685     constructor: YUITest.AssertionError,
2687     /**
2688      * Returns a fully formatted error for an assertion failure. This should
2689      * be overridden by all subclasses to provide specific information.
2690      * @method getMessage
2691      * @return {String} A string describing the error.
2692      */
2693     getMessage : function () {
2694         return this.message;
2695     },
2696     
2697     /**
2698      * Returns a string representation of the error.
2699      * @method toString
2700      * @return {String} A string representation of the error.
2701      */
2702     toString : function () {
2703         return this.name + ": " + this.getMessage();
2704     }
2708  * ComparisonFailure is subclass of Error that is thrown whenever
2709  * a comparison between two values fails. It provides mechanisms to retrieve
2710  * both the expected and actual value.
2712  * @param {String} message The message to display when the error occurs.
2713  * @param {Object} expected The expected value.
2714  * @param {Object} actual The actual value that caused the assertion to fail.
2715  * @namespace Test 
2716  * @extends AssertionError
2717  * @class ComparisonFailure
2718  * @constructor
2719  */ 
2720 YUITest.ComparisonFailure = function (message, expected, actual){
2722     //call superclass
2723     YUITest.AssertionError.call(this, message);
2724     
2725     /**
2726      * The expected value.
2727      * @type Object
2728      * @property expected
2729      */
2730     this.expected = expected;
2731     
2732     /**
2733      * The actual value.
2734      * @type Object
2735      * @property actual
2736      */
2737     this.actual = actual;
2738     
2739     /**
2740      * The name of the error that occurred.
2741      * @type String
2742      * @property name
2743      */
2744     this.name = "ComparisonFailure";
2745     
2748 //inherit from YUITest.AssertionError
2749 YUITest.ComparisonFailure.prototype = new YUITest.AssertionError;
2751 //restore constructor
2752 YUITest.ComparisonFailure.prototype.constructor = YUITest.ComparisonFailure;
2755  * Returns a fully formatted error for an assertion failure. This message
2756  * provides information about the expected and actual values.
2757  * @method getMessage
2758  * @return {String} A string describing the error.
2759  */
2760 YUITest.ComparisonFailure.prototype.getMessage = function(){
2761     return this.message + "\nExpected: " + this.expected + " (" + (typeof this.expected) + ")"  +
2762             "\nActual: " + this.actual + " (" + (typeof this.actual) + ")";
2765  * An object object containing coverage result formatting methods.
2766  * @namespace Test
2767  * @class CoverageFormat
2768  * @static
2769  */
2770 YUITest.CoverageFormat = {
2772     /**
2773      * Returns the coverage report in JSON format. This is the straight
2774      * JSON representation of the native coverage report.
2775      * @param {Object} coverage The coverage report object.
2776      * @return {String} A JSON-formatted string of coverage data.
2777      * @method JSON
2778      * @namespace Test.CoverageFormat
2779      */
2780     JSON: function(coverage){
2781         return YUITest.Util.JSON.stringify(coverage);
2782     },
2783     
2784     /**
2785      * Returns the coverage report in a JSON format compatible with
2786      * Xdebug. See <a href="http://www.xdebug.com/docs/code_coverage">Xdebug Documentation</a>
2787      * for more information. Note: function coverage is not available
2788      * in this format.
2789      * @param {Object} coverage The coverage report object.
2790      * @return {String} A JSON-formatted string of coverage data.
2791      * @method XdebugJSON
2792      * @namespace Test.CoverageFormat
2793      */    
2794     XdebugJSON: function(coverage){
2795     
2796         var report = {};
2797         for (var prop in coverage){
2798             if (coverage.hasOwnProperty(prop)){
2799                 report[prop] = coverage[prop].lines;
2800             }
2801         }
2803         return YUITest.Util.JSON.stringify(coverage);
2804     }
2810  * The DateAssert object provides functions to test JavaScript Date objects
2811  * for a variety of cases.
2812  * @namespace Test
2813  * @class DateAssert
2814  * @static
2815  */
2817 YUITest.DateAssert = {
2819     /**
2820      * Asserts that a date's month, day, and year are equal to another date's.
2821      * @param {Date} expected The expected date.
2822      * @param {Date} actual The actual date to test.
2823      * @param {String} message (Optional) The message to display if the assertion fails.
2824      * @method datesAreEqual
2825      * @static
2826      */
2827     datesAreEqual : function (expected, actual, message){
2828         YUITest.Assert._increment();        
2829         if (expected instanceof Date && actual instanceof Date){
2830             var msg = "";
2831             
2832             //check years first
2833             if (expected.getFullYear() != actual.getFullYear()){
2834                 msg = "Years should be equal.";
2835             }
2836             
2837             //now check months
2838             if (expected.getMonth() != actual.getMonth()){
2839                 msg = "Months should be equal.";
2840             }                
2841             
2842             //last, check the day of the month
2843             if (expected.getDate() != actual.getDate()){
2844                 msg = "Days of month should be equal.";
2845             }                
2846             
2847             if (msg.length){
2848                 throw new YUITest.ComparisonFailure(YUITest.Assert._formatMessage(message, msg), expected, actual);
2849             }
2850         } else {
2851             throw new TypeError("YUITest.DateAssert.datesAreEqual(): Expected and actual values must be Date objects.");
2852         }
2853     },
2855     /**
2856      * Asserts that a date's hour, minutes, and seconds are equal to another date's.
2857      * @param {Date} expected The expected date.
2858      * @param {Date} actual The actual date to test.
2859      * @param {String} message (Optional) The message to display if the assertion fails.
2860      * @method timesAreEqual
2861      * @static
2862      */
2863     timesAreEqual : function (expected, actual, message){
2864         YUITest.Assert._increment();
2865         if (expected instanceof Date && actual instanceof Date){
2866             var msg = "";
2867             
2868             //check hours first
2869             if (expected.getHours() != actual.getHours()){
2870                 msg = "Hours should be equal.";
2871             }
2872             
2873             //now check minutes
2874             if (expected.getMinutes() != actual.getMinutes()){
2875                 msg = "Minutes should be equal.";
2876             }                
2877             
2878             //last, check the seconds
2879             if (expected.getSeconds() != actual.getSeconds()){
2880                 msg = "Seconds should be equal.";
2881             }                
2882             
2883             if (msg.length){
2884                 throw new YUITest.ComparisonFailure(YUITest.Assert._formatMessage(message, msg), expected, actual);
2885             }
2886         } else {
2887             throw new TypeError("YUITest.DateAssert.timesAreEqual(): Expected and actual values must be Date objects.");
2888         }
2889     }
2890     
2893  * Creates a new mock object.
2894  * @namespace Test
2895  * @class Mock
2896  * @constructor
2897  * @param {Object} template (Optional) An object whose methods
2898  *      should be stubbed out on the mock object.
2899  */
2900 YUITest.Mock = function(template){
2902     //use blank object is nothing is passed in
2903     template = template || {};
2904     
2905     var mock,
2906         name;
2907     
2908     //try to create mock that keeps prototype chain intact
2909     //fails in the case of ActiveX objects
2910     try {
2911         function f(){}
2912         f.prototype = template;
2913         mock = new f();
2914     } catch (ex) {
2915         mock = {};
2916     }
2918     //create stubs for all methods
2919     for (name in template){
2920         if (template.hasOwnProperty(name)){
2921             if (typeof template[name] == "function"){
2922                 mock[name] = function(name){
2923                     return function(){
2924                         YUITest.Assert.fail("Method " + name + "() was called but was not expected to be.");
2925                     };
2926                 }(name);
2927             }
2928         }
2929     }
2931     //return it
2932     return mock;    
2934     
2936  * Assigns an expectation to a mock object. This is used to create
2937  * methods and properties on the mock object that are monitored for
2938  * calls and changes, respectively.
2939  * @param {Object} mock The object to add the expectation to.
2940  * @param {Object} expectation An object defining the expectation. For
2941  *      a method, the keys "method" and "args" are required with
2942  *      an optional "returns" key available. For properties, the keys
2943  *      "property" and "value" are required.
2944  * @return {void}
2945  * @method expect
2946  * @static
2947  */ 
2948 YUITest.Mock.expect = function(mock /*:Object*/, expectation /*:Object*/){
2950     //make sure there's a place to store the expectations
2951     if (!mock.__expectations) {
2952         mock.__expectations = {};
2953     }
2955     //method expectation
2956     if (expectation.method){
2957         var name = expectation.method,
2958             args = expectation.args || [],
2959             result = expectation.returns,
2960             callCount = (typeof expectation.callCount == "number") ? expectation.callCount : 1,
2961             error = expectation.error,
2962             run = expectation.run || function(){},
2963             i;
2964             
2965         //save expectations
2966         mock.__expectations[name] = expectation;
2967         expectation.callCount = callCount;
2968         expectation.actualCallCount = 0;
2969             
2970         //process arguments
2971         for (i=0; i < args.length; i++){
2972              if (!(args[i] instanceof YUITest.Mock.Value)){
2973                 args[i] = YUITest.Mock.Value(YUITest.Assert.areSame, [args[i]], "Argument " + i + " of " + name + "() is incorrect.");
2974             }       
2975         }
2976     
2977         //if the method is expected to be called
2978         if (callCount > 0){
2979             mock[name] = function(){   
2980                 try {
2981                     expectation.actualCallCount++;
2982                     YUITest.Assert.areEqual(args.length, arguments.length, "Method " + name + "() passed incorrect number of arguments.");
2983                     for (var i=0, len=args.length; i < len; i++){
2984                         args[i].verify(arguments[i]);
2985                     }                
2987                     run.apply(this, arguments);
2988                     
2989                     if (error){
2990                         throw error;
2991                     }
2992                 } catch (ex){
2993                     //route through TestRunner for proper handling
2994                     YUITest.TestRunner._handleError(ex);
2995                 }
2996                 
2997                 return result;
2998             };
2999         } else {
3000         
3001             //method should fail if called when not expected
3002             mock[name] = function(){
3003                 try {
3004                     YUITest.Assert.fail("Method " + name + "() should not have been called.");
3005                 } catch (ex){
3006                     //route through TestRunner for proper handling
3007                     YUITest.TestRunner._handleError(ex);
3008                 }                    
3009             };
3010         }
3011     } else if (expectation.property){
3012         //save expectations
3013         mock.__expectations[expectation.property] = expectation;
3014     }
3018  * Verifies that all expectations of a mock object have been met and
3019  * throws an assertion error if not.
3020  * @param {Object} mock The object to verify..
3021  * @return {void}
3022  * @method verify
3023  * @static
3024  */ 
3025 YUITest.Mock.verify = function(mock){    
3026     try {
3027     
3028         for (var name in mock.__expectations){
3029             if (mock.__expectations.hasOwnProperty(name)){
3030                 var expectation = mock.__expectations[name];
3031                 if (expectation.method) {
3032                     YUITest.Assert.areEqual(expectation.callCount, expectation.actualCallCount, "Method " + expectation.method + "() wasn't called the expected number of times.");
3033                 } else if (expectation.property){
3034                     YUITest.Assert.areEqual(expectation.value, mock[expectation.property], "Property " + expectation.property + " wasn't set to the correct value."); 
3035                 }                
3036             }
3037         }
3039     } catch (ex){
3040         //route through TestRunner for proper handling
3041         YUITest.TestRunner._handleError(ex);
3042     }
3046  * Creates a new value matcher.
3047  * @param {Function} method The function to call on the value.
3048  * @param {Array} originalArgs (Optional) Array of arguments to pass to the method.
3049  * @param {String} message (Optional) Message to display in case of failure.
3050  * @namespace Test.Mock
3051  * @class Value
3052  * @constructor
3053  */
3054 YUITest.Mock.Value = function(method, originalArgs, message){
3055     if (this instanceof YUITest.Mock.Value){
3056         this.verify = function(value){
3057             var args = [].concat(originalArgs || []);
3058             args.push(value);
3059             args.push(message);
3060             method.apply(null, args);
3061         };
3062     } else {
3063         return new YUITest.Mock.Value(method, originalArgs, message);
3064     }
3068  * Predefined matcher to match any value.
3069  * @property Any
3070  * @static
3071  * @type Function
3072  */
3073 YUITest.Mock.Value.Any        = YUITest.Mock.Value(function(){});
3076  * Predefined matcher to match boolean values.
3077  * @property Boolean
3078  * @static
3079  * @type Function
3080  */
3081 YUITest.Mock.Value.Boolean    = YUITest.Mock.Value(YUITest.Assert.isBoolean);
3084  * Predefined matcher to match number values.
3085  * @property Number
3086  * @static
3087  * @type Function
3088  */
3089 YUITest.Mock.Value.Number     = YUITest.Mock.Value(YUITest.Assert.isNumber);
3092  * Predefined matcher to match string values.
3093  * @property String
3094  * @static
3095  * @type Function
3096  */
3097 YUITest.Mock.Value.String     = YUITest.Mock.Value(YUITest.Assert.isString);
3100  * Predefined matcher to match object values.
3101  * @property Object
3102  * @static
3103  * @type Function
3104  */
3105 YUITest.Mock.Value.Object     = YUITest.Mock.Value(YUITest.Assert.isObject);
3108  * Predefined matcher to match function values.
3109  * @property Function
3110  * @static
3111  * @type Function
3112  */
3113 YUITest.Mock.Value.Function   = YUITest.Mock.Value(YUITest.Assert.isFunction);
3116  * The ObjectAssert object provides functions to test JavaScript objects
3117  * for a variety of cases.
3118  * @namespace Test
3119  * @class ObjectAssert
3120  * @static
3121  */
3122 YUITest.ObjectAssert = {
3124     /**
3125      * Asserts that an object has all of the same properties
3126      * and property values as the other.
3127      * @param {Object} expected The object with all expected properties and values.
3128      * @param {Object} actual The object to inspect.
3129      * @param {String} message (Optional) The message to display if the assertion fails.
3130      * @method areEqual
3131      * @static
3132      * @deprecated
3133      */
3134     areEqual: function(expected, actual, message) {
3135         YUITest.Assert._increment();         
3136         
3137         var expectedKeys = YUITest.Object.keys(expected),
3138             actualKeys = YUITest.Object.keys(actual);
3139         
3140         //first check keys array length
3141         if (expectedKeys.length != actualKeys.length){
3142             YUITest.Assert.fail(YUITest.Assert._formatMessage(message, "Object should have " + expectedKeys.length + " keys but has " + actualKeys.length));
3143         }
3144         
3145         //then check values
3146         for (var name in expected){
3147             if (expected.hasOwnProperty(name)){
3148                 if (expected[name] != actual[name]){
3149                     throw new YUITest.ComparisonFailure(YUITest.Assert._formatMessage(message, "Values should be equal for property " + name), expected[name], actual[name]);
3150                 }            
3151             }
3152         }           
3153     },
3154     
3155     /**
3156      * Asserts that an object has a property with the given name.
3157      * @param {String} propertyName The name of the property to test.
3158      * @param {Object} object The object to search.
3159      * @param {String} message (Optional) The message to display if the assertion fails.
3160      * @method hasKey
3161      * @static
3162      * @deprecated Use ownsOrInheritsKey() instead
3163      */    
3164     hasKey: function (propertyName, object, message) {
3165         YUITest.ObjectAssert.ownsOrInheritsKey(propertyName, object, message);   
3166     },
3167     
3168     /**
3169      * Asserts that an object has all properties of a reference object.
3170      * @param {Array} properties An array of property names that should be on the object.
3171      * @param {Object} object The object to search.
3172      * @param {String} message (Optional) The message to display if the assertion fails.
3173      * @method hasKeys
3174      * @static
3175      * @deprecated Use ownsOrInheritsKeys() instead
3176      */    
3177     hasKeys: function (properties, object, message) {
3178         YUITest.ObjectAssert.ownsOrInheritsKeys(properties, object, message);
3179     },
3180     
3181     /**
3182      * Asserts that a property with the given name exists on an object's prototype.
3183      * @param {String} propertyName The name of the property to test.
3184      * @param {Object} object The object to search.
3185      * @param {String} message (Optional) The message to display if the assertion fails.
3186      * @method inheritsKey
3187      * @static
3188      */    
3189     inheritsKey: function (propertyName, object, message) {
3190         YUITest.Assert._increment();               
3191         if (!(propertyName in object && !object.hasOwnProperty(propertyName))){
3192             YUITest.Assert.fail(YUITest.Assert._formatMessage(message, "Property '" + propertyName + "' not found on object instance."));
3193         }     
3194     },
3195     
3196     /**
3197      * Asserts that all properties exist on an object prototype.
3198      * @param {Array} properties An array of property names that should be on the object.
3199      * @param {Object} object The object to search.
3200      * @param {String} message (Optional) The message to display if the assertion fails.
3201      * @method inheritsKeys
3202      * @static
3203      */    
3204     inheritsKeys: function (properties, object, message) {
3205         YUITest.Assert._increment();        
3206         for (var i=0; i < properties.length; i++){
3207             if (!(propertyName in object && !object.hasOwnProperty(properties[i]))){
3208                 YUITest.Assert.fail(YUITest.Assert._formatMessage(message, "Property '" + properties[i] + "' not found on object instance."));
3209             }      
3210         }
3211     },
3212     
3213     /**
3214      * Asserts that a property with the given name exists on an object instance (not on its prototype).
3215      * @param {String} propertyName The name of the property to test.
3216      * @param {Object} object The object to search.
3217      * @param {String} message (Optional) The message to display if the assertion fails.
3218      * @method ownsKey
3219      * @static
3220      */    
3221     ownsKey: function (propertyName, object, message) {
3222         YUITest.Assert._increment();               
3223         if (!object.hasOwnProperty(propertyName)){
3224             YUITest.Assert.fail(YUITest.Assert._formatMessage(message, "Property '" + propertyName + "' not found on object instance."));
3225         }     
3226     },
3227     
3228     /**
3229      * Asserts that all properties exist on an object instance (not on its prototype).
3230      * @param {Array} properties An array of property names that should be on the object.
3231      * @param {Object} object The object to search.
3232      * @param {String} message (Optional) The message to display if the assertion fails.
3233      * @method ownsKeys
3234      * @static
3235      */    
3236     ownsKeys: function (properties, object, message) {
3237         YUITest.Assert._increment();        
3238         for (var i=0; i < properties.length; i++){
3239             if (!object.hasOwnProperty(properties[i])){
3240                 YUITest.Assert.fail(YUITest.Assert._formatMessage(message, "Property '" + properties[i] + "' not found on object instance."));
3241             }      
3242         }
3243     },
3244     
3245     /**
3246      * Asserts that an object owns no properties.
3247      * @param {Object} object The object to check.
3248      * @param {String} message (Optional) The message to display if the assertion fails.
3249      * @method ownsNoKeys
3250      * @static
3251      */    
3252     ownsNoKeys : function (object, message) {
3253         YUITest.Assert._increment();  
3254         var count = 0,
3255             name;
3256         for (name in object){
3257             if (object.hasOwnProperty(name)){
3258                 count++;
3259             }
3260         }
3261         
3262         if (count !== 0){
3263             YUITest.Assert.fail(YUITest.Assert._formatMessage(message, "Object owns " + count + " properties but should own none."));        
3264         }
3266     },
3268     /**
3269      * Asserts that an object has a property with the given name.
3270      * @param {String} propertyName The name of the property to test.
3271      * @param {Object} object The object to search.
3272      * @param {String} message (Optional) The message to display if the assertion fails.
3273      * @method ownsOrInheritsKey
3274      * @static
3275      */    
3276     ownsOrInheritsKey: function (propertyName, object, message) {
3277         YUITest.Assert._increment();               
3278         if (!(propertyName in object)){
3279             YUITest.Assert.fail(YUITest.Assert._formatMessage(message, "Property '" + propertyName + "' not found on object."));
3280         }    
3281     },
3282     
3283     /**
3284      * Asserts that an object has all properties of a reference object.
3285      * @param {Array} properties An array of property names that should be on the object.
3286      * @param {Object} object The object to search.
3287      * @param {String} message (Optional) The message to display if the assertion fails.
3288      * @method ownsOrInheritsKeys
3289      * @static
3290      */    
3291     ownsOrInheritsKeys: function (properties, object, message) {
3292         YUITest.Assert._increment();  
3293         for (var i=0; i < properties.length; i++){
3294             if (!(properties[i] in object)){
3295                 YUITest.Assert.fail(YUITest.Assert._formatMessage(message, "Property '" + properties[i] + "' not found on object."));
3296             }      
3297         }
3298     }    
3301  * Convenience type for storing and aggregating
3302  * test result information.
3303  * @private
3304  * @namespace Test
3305  * @class Results
3306  * @constructor
3307  * @param {String} name The name of the test.
3308  */
3309 YUITest.Results = function(name){
3311     /**
3312      * Name of the test, test case, or test suite.
3313      * @type String
3314      * @property name
3315      */
3316     this.name = name;
3317     
3318     /**
3319      * Number of passed tests.
3320      * @type int
3321      * @property passed
3322      */
3323     this.passed = 0;
3324     
3325     /**
3326      * Number of failed tests.
3327      * @type int
3328      * @property failed
3329      */
3330     this.failed = 0;
3331     
3332     /**
3333      * Number of errors that occur in non-test methods.
3334      * @type int
3335      * @property errors
3336      */
3337     this.errors = 0;
3338     
3339     /**
3340      * Number of ignored tests.
3341      * @type int
3342      * @property ignored
3343      */
3344     this.ignored = 0;
3345     
3346     /**
3347      * Number of total tests.
3348      * @type int
3349      * @property total
3350      */
3351     this.total = 0;
3352     
3353     /**
3354      * Amount of time (ms) it took to complete testing.
3355      * @type int
3356      * @property duration
3357      */
3358     this.duration = 0;
3362  * Includes results from another results object into this one.
3363  * @param {Test.Results} result The results object to include.
3364  * @method include
3365  * @return {void}
3366  */
3367 YUITest.Results.prototype.include = function(results){
3368     this.passed += results.passed;
3369     this.failed += results.failed;
3370     this.ignored += results.ignored;
3371     this.total += results.total;
3372     this.errors += results.errors;
3375  * ShouldError is subclass of Error that is thrown whenever
3376  * a test is expected to throw an error but doesn't.
3378  * @param {String} message The message to display when the error occurs.
3379  * @namespace Test 
3380  * @extends AssertionError
3381  * @class ShouldError
3382  * @constructor
3383  */ 
3384 YUITest.ShouldError = function (message){
3386     //call superclass
3387     YUITest.AssertionError.call(this, message || "This test should have thrown an error but didn't.");
3388     
3389     /**
3390      * The name of the error that occurred.
3391      * @type String
3392      * @property name
3393      */
3394     this.name = "ShouldError";
3395     
3398 //inherit from YUITest.AssertionError
3399 YUITest.ShouldError.prototype = new YUITest.AssertionError();
3401 //restore constructor
3402 YUITest.ShouldError.prototype.constructor = YUITest.ShouldError;
3404  * ShouldFail is subclass of AssertionError that is thrown whenever
3405  * a test was expected to fail but did not.
3407  * @param {String} message The message to display when the error occurs.
3408  * @namespace Test 
3409  * @extends YUITest.AssertionError
3410  * @class ShouldFail
3411  * @constructor
3412  */ 
3413 YUITest.ShouldFail = function (message){
3415     //call superclass
3416     YUITest.AssertionError.call(this, message || "This test should fail but didn't.");
3417     
3418     /**
3419      * The name of the error that occurred.
3420      * @type String
3421      * @property name
3422      */
3423     this.name = "ShouldFail";
3424     
3427 //inherit from YUITest.AssertionError
3428 YUITest.ShouldFail.prototype = new YUITest.AssertionError();
3430 //restore constructor
3431 YUITest.ShouldFail.prototype.constructor = YUITest.ShouldFail;
3433  * UnexpectedError is subclass of AssertionError that is thrown whenever
3434  * an error occurs within the course of a test and the test was not expected
3435  * to throw an error.
3437  * @param {Error} cause The unexpected error that caused this error to be 
3438  *                      thrown.
3439  * @namespace Test 
3440  * @extends YUITest.AssertionError
3441  * @class UnexpectedError
3442  * @constructor
3443  */  
3444 YUITest.UnexpectedError = function (cause){
3446     //call superclass
3447     YUITest.AssertionError.call(this, "Unexpected error: " + cause.message);
3448     
3449     /**
3450      * The unexpected error that occurred.
3451      * @type Error
3452      * @property cause
3453      */
3454     this.cause = cause;
3455     
3456     /**
3457      * The name of the error that occurred.
3458      * @type String
3459      * @property name
3460      */
3461     this.name = "UnexpectedError";
3462     
3463     /**
3464      * Stack information for the error (if provided).
3465      * @type String
3466      * @property stack
3467      */
3468     this.stack = cause.stack;
3469     
3472 //inherit from YUITest.AssertionError
3473 YUITest.UnexpectedError.prototype = new YUITest.AssertionError();
3475 //restore constructor
3476 YUITest.UnexpectedError.prototype.constructor = YUITest.UnexpectedError;
3478  * UnexpectedValue is subclass of Error that is thrown whenever
3479  * a value was unexpected in its scope. This typically means that a test
3480  * was performed to determine that a value was *not* equal to a certain
3481  * value.
3483  * @param {String} message The message to display when the error occurs.
3484  * @param {Object} unexpected The unexpected value.
3485  * @namespace Test 
3486  * @extends AssertionError
3487  * @class UnexpectedValue
3488  * @constructor
3489  */ 
3490 YUITest.UnexpectedValue = function (message, unexpected){
3492     //call superclass
3493     YUITest.AssertionError.call(this, message);
3494     
3495     /**
3496      * The unexpected value.
3497      * @type Object
3498      * @property unexpected
3499      */
3500     this.unexpected = unexpected;
3501     
3502     /**
3503      * The name of the error that occurred.
3504      * @type String
3505      * @property name
3506      */
3507     this.name = "UnexpectedValue";
3508     
3511 //inherit from YUITest.AssertionError
3512 YUITest.UnexpectedValue.prototype = new YUITest.AssertionError();
3514 //restore constructor
3515 YUITest.UnexpectedValue.prototype.constructor = YUITest.UnexpectedValue;
3518  * Returns a fully formatted error for an assertion failure. This message
3519  * provides information about the expected and actual values.
3520  * @method getMessage
3521  * @return {String} A string describing the error.
3522  */
3523 YUITest.UnexpectedValue.prototype.getMessage = function(){
3524     return this.message + "\nUnexpected: " + this.unexpected + " (" + (typeof this.unexpected) + ") ";
3528  * Represents a stoppage in test execution to wait for an amount of time before
3529  * continuing.
3530  * @param {Function} segment A function to run when the wait is over.
3531  * @param {int} delay The number of milliseconds to wait before running the code.
3532  * @class Wait
3533  * @namespace Test
3534  * @constructor
3536  */
3537 YUITest.Wait = function (segment, delay) {
3538     
3539     /**
3540      * The segment of code to run when the wait is over.
3541      * @type Function
3542      * @property segment
3543      */
3544     this.segment = (typeof segment == "function" ? segment : null);
3546     /**
3547      * The delay before running the segment of code.
3548      * @type int
3549      * @property delay
3550      */
3551     this.delay = (typeof delay == "number" ? delay : 0);        
3555 //Setting up our aliases..
3556 Y.Test = YUITest;
3557 Y.Object.each(YUITest, function(item, name) {
3558     var name = name.replace('Test', '');
3559     Y.Test[name] = item;
3562 Y.Assert = YUITest.Assert;
3563 Y.Assert.Error = Y.Test.AssertionError;
3564 Y.Assert.ComparisonFailure = Y.Test.ComparisonFailure;
3565 Y.Assert.UnexpectedValue = Y.Test.UnexpectedValue;
3566 Y.Mock = Y.Test.Mock;
3567 Y.ObjectAssert = Y.Test.ObjectAssert;
3568 Y.ArrayAssert = Y.Test.ArrayAssert;
3569 Y.DateAssert = Y.Test.DateAssert;
3570 Y.Test.ResultsFormat = Y.Test.TestFormat;
3573  * Asserts that a given condition is true. If not, then a Y.Assert.Error object is thrown
3574  * and the test fails.
3575  * @method assert
3576  * @param {Boolean} condition The condition to test.
3577  * @param {String} message The message to display if the assertion fails.
3578  * @for YUI
3579  * @static
3580  */
3581 Y.assert = function(condition, message){
3582     Y.Assert._increment();
3583     if (!condition){
3584         throw new Y.Assert.Error(Y.Assert._formatMessage(message, "Assertion failed."));
3585     }
3589  * Forces an assertion error to occur. Shortcut for Y.Assert.fail().
3590  * @method fail
3591  * @param {String} message (Optional) The message to display with the failure.
3592  * @for YUI
3593  * @static
3594  */
3595 Y.fail = Y.Assert.fail; 
3597 var logEvent = function(event) {
3598     
3599     //data variables
3600     var message = "";
3601     var messageType = "";
3602     
3603     switch(event.type){
3604         case this.BEGIN_EVENT:
3605             message = "Testing began at " + (new Date()).toString() + ".";
3606             messageType = "info";
3607             break;
3608             
3609         case this.COMPLETE_EVENT:
3610             message = Y.substitute("Testing completed at " +
3611                 (new Date()).toString() + ".\n" +
3612                 "Passed:{passed} Failed:{failed} " +
3613                 "Total:{total} ({ignored} ignored)",
3614                 event.results);
3615             messageType = "info";
3616             break;
3617             
3618         case this.TEST_FAIL_EVENT:
3619             message = event.testName + ": failed.\n" + event.error.getMessage();
3620             messageType = "fail";
3621             break;
3622             
3623         case this.TEST_IGNORE_EVENT:
3624             message = event.testName + ": ignored.";
3625             messageType = "ignore";
3626             break;
3627             
3628         case this.TEST_PASS_EVENT:
3629             message = event.testName + ": passed.";
3630             messageType = "pass";
3631             break;
3632             
3633         case this.TEST_SUITE_BEGIN_EVENT:
3634             message = "Test suite \"" + event.testSuite.name + "\" started.";
3635             messageType = "info";
3636             break;
3637             
3638         case this.TEST_SUITE_COMPLETE_EVENT:
3639             message = Y.substitute("Test suite \"" +
3640                 event.testSuite.name + "\" completed" + ".\n" +
3641                 "Passed:{passed} Failed:{failed} " +
3642                 "Total:{total} ({ignored} ignored)",
3643                 event.results);
3644             messageType = "info";
3645             break;
3646             
3647         case this.TEST_CASE_BEGIN_EVENT:
3648             message = "Test case \"" + event.testCase.name + "\" started.";
3649             messageType = "info";
3650             break;
3651             
3652         case this.TEST_CASE_COMPLETE_EVENT:
3653             message = Y.substitute("Test case \"" +
3654                 event.testCase.name + "\" completed.\n" +
3655                 "Passed:{passed} Failed:{failed} " +
3656                 "Total:{total} ({ignored} ignored)",
3657                 event.results);
3658             messageType = "info";
3659             break;
3660         default:
3661             message = "Unexpected event " + event.type;
3662             message = "info";
3663     }
3664     
3665     if (Y.Test.Runner._log) {
3666         Y.log(message, messageType, "TestRunner");
3667     }
3670 var i, name;
3672 for (i in Y.Test.Runner) {
3673     name = Y.Test.Runner[i];
3674     if (i.indexOf('_EVENT') > -1) {
3675         Y.Test.Runner.subscribe(name, logEvent);
3676     }
3679 Y.Test.Runner.once = Y.Test.Runner.subscribe;
3681 Y.Test.Runner.disableLogging = function() {
3682     Y.Test.Runner._log = false;
3685 Y.Test.Runner.enableLogging = function() {
3686     Y.Test.Runner._log = true;
3689 Y.Test.Runner._ignoreEmpty = true;
3690 Y.Test.Runner._log = true;
3692 Y.Test.Runner.on = Y.Test.Runner.attach;
3694 if (Y.config.win) {
3695     Y.config.win.YUITest = YUITest;
3700 }, '3.5.0' ,{requires:['event-simulate','event-custom','substitute','json-stringify']});