]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - include/javascript/yui/build/yuitest/yuitest.js
Release 6.5.0
[Github/sugarcrm.git] / include / javascript / yui / build / yuitest / yuitest.js
1 /*
2 Copyright (c) 2011, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.com/yui/license.html
5 version: 2.9.0
6 */
7 YAHOO.namespace("tool");
8
9 //-----------------------------------------------------------------------------
10 // TestCase object
11 //-----------------------------------------------------------------------------
12 (function(){
13     
14     //used for autogenerating test case names
15     var tempId = 0;
16     
17     /**
18      * Test case containing various tests to run.
19      * @param template An object containing any number of test methods, other methods,
20      *                 an optional name, and anything else the test case needs.
21      * @class TestCase
22      * @namespace YAHOO.tool
23      * @constructor
24      */
25     YAHOO.tool.TestCase = function (template /*:Object*/) {
26         
27         /**
28          * Special rules for the test case. Possible subobjects
29          * are fail, for tests that should fail, and error, for
30          * tests that should throw an error.
31          */
32         this._should /*:Object*/ = {};
33         
34         //copy over all properties from the template to this object
35         for (var prop in template) {
36             this[prop] = template[prop];
37         }    
38         
39         //check for a valid name
40         if (!YAHOO.lang.isString(this.name)){
41             /**
42              * Name for the test case.
43              */
44             this.name /*:String*/ = "testCase" + (tempId++);
45         }
46     
47     };
48     
49     
50     YAHOO.tool.TestCase.prototype = {  
51     
52         /**
53          * Resumes a paused test and runs the given function.
54          * @param {Function} segment (Optional) The function to run.
55          *      If omitted, the test automatically passes.
56          * @return {Void}
57          * @method resume
58          */
59         resume : function (segment /*:Function*/) /*:Void*/ {
60             YAHOO.tool.TestRunner.resume(segment);
61         },
62     
63         /**
64          * Causes the test case to wait a specified amount of time and then
65          * continue executing the given code.
66          * @param {Function} segment (Optional) The function to run after the delay.
67          *      If omitted, the TestRunner will wait until resume() is called.
68          * @param {int} delay (Optional) The number of milliseconds to wait before running
69          *      the function. If omitted, defaults to zero.
70          * @return {Void}
71          * @method wait
72          */
73         wait : function (segment /*:Function*/, delay /*:int*/) /*:Void*/{
74             var args = arguments;
75             if (YAHOO.lang.isFunction(args[0])){
76                 throw new YAHOO.tool.TestCase.Wait(args[0], args[1]);
77             } else {
78                 throw new YAHOO.tool.TestCase.Wait(function(){
79                     YAHOO.util.Assert.fail("Timeout: wait() called but resume() never called.");
80                 }, (YAHOO.lang.isNumber(args[0]) ? args[0] : 10000));
81             }            
82         },
83     
84         //-------------------------------------------------------------------------
85         // Stub Methods
86         //-------------------------------------------------------------------------
87     
88         /**
89          * Function to run before each test is executed.
90          * @return {Void}
91          * @method setUp
92          */
93         setUp : function () /*:Void*/ {
94         },
95         
96         /**
97          * Function to run after each test is executed.
98          * @return {Void}
99          * @method tearDown
100          */
101         tearDown: function () /*:Void*/ {    
102         }
103     };
104     
105     /**
106      * Represents a stoppage in test execution to wait for an amount of time before
107      * continuing.
108      * @param {Function} segment A function to run when the wait is over.
109      * @param {int} delay The number of milliseconds to wait before running the code.
110      * @class Wait
111      * @namespace YAHOO.tool.TestCase
112      * @constructor
113      *
114      */
115     YAHOO.tool.TestCase.Wait = function (segment /*:Function*/, delay /*:int*/) {
116         
117         /**
118          * The segment of code to run when the wait is over.
119          * @type Function
120          * @property segment
121          */
122         this.segment /*:Function*/ = (YAHOO.lang.isFunction(segment) ? segment : null);
123     
124         /**
125          * The delay before running the segment of code.
126          * @type int
127          * @property delay
128          */
129         this.delay /*:int*/ = (YAHOO.lang.isNumber(delay) ? delay : 0);
130     
131     };
132
133 })();
134 YAHOO.namespace("tool");
135
136
137 //-----------------------------------------------------------------------------
138 // TestSuite object
139 //-----------------------------------------------------------------------------
140
141 /**
142  * A test suite that can contain a collection of TestCase and TestSuite objects.
143  * @param {String||Object} data The name of the test suite or an object containing
144  *      a name property as well as setUp and tearDown methods.
145  * @namespace YAHOO.tool
146  * @class TestSuite
147  * @constructor
148  */
149 YAHOO.tool.TestSuite = function (data /*:String||Object*/) {
150
151     /**
152      * The name of the test suite.
153      * @type String
154      * @property name
155      */
156     this.name /*:String*/ = "";
157
158     /**
159      * Array of test suites and
160      * @private
161      */
162     this.items /*:Array*/ = [];
163
164     //initialize the properties
165     if (YAHOO.lang.isString(data)){
166         this.name = data;
167     } else if (YAHOO.lang.isObject(data)){
168         YAHOO.lang.augmentObject(this, data, true);
169     }
170
171     //double-check name
172     if (this.name === ""){
173         this.name = YAHOO.util.Dom.generateId(null, "testSuite");
174     }
175
176 };
177
178 YAHOO.tool.TestSuite.prototype = {
179     
180     /**
181      * Adds a test suite or test case to the test suite.
182      * @param {YAHOO.tool.TestSuite||YAHOO.tool.TestCase} testObject The test suite or test case to add.
183      * @return {Void}
184      * @method add
185      */
186     add : function (testObject /*:YAHOO.tool.TestSuite*/) /*:Void*/ {
187         if (testObject instanceof YAHOO.tool.TestSuite || testObject instanceof YAHOO.tool.TestCase) {
188             this.items.push(testObject);
189         }
190     },
191     
192     //-------------------------------------------------------------------------
193     // Stub Methods
194     //-------------------------------------------------------------------------
195
196     /**
197      * Function to run before each test is executed.
198      * @return {Void}
199      * @method setUp
200      */
201     setUp : function () /*:Void*/ {
202     },
203     
204     /**
205      * Function to run after each test is executed.
206      * @return {Void}
207      * @method tearDown
208      */
209     tearDown: function () /*:Void*/ {
210     }
211     
212 };
213 YAHOO.namespace("tool");
214
215 /**
216  * The YUI test tool
217  * @module yuitest
218  * @namespace YAHOO.tool
219  * @requires yahoo,dom,event,logger
220  * @optional event-simulate
221  */
222
223
224 //-----------------------------------------------------------------------------
225 // TestRunner object
226 //-----------------------------------------------------------------------------
227
228
229 YAHOO.tool.TestRunner = (function(){
230
231     /**
232      * A node in the test tree structure. May represent a TestSuite, TestCase, or
233      * test function.
234      * @param {Variant} testObject A TestSuite, TestCase, or the name of a test function.
235      * @class TestNode
236      * @constructor
237      * @private
238      */
239     function TestNode(testObject /*:Variant*/){
240     
241         /**
242          * The TestSuite, TestCase, or test function represented by this node.
243          * @type Variant
244          * @property testObject
245          */
246         this.testObject = testObject;
247         
248         /**
249          * Pointer to this node's first child.
250          * @type TestNode
251          * @property firstChild
252          */        
253         this.firstChild /*:TestNode*/ = null;
254         
255         /**
256          * Pointer to this node's last child.
257          * @type TestNode
258          * @property lastChild
259          */        
260         this.lastChild = null;
261         
262         /**
263          * Pointer to this node's parent.
264          * @type TestNode
265          * @property parent
266          */        
267         this.parent = null; 
268    
269         /**
270          * Pointer to this node's next sibling.
271          * @type TestNode
272          * @property next
273          */        
274         this.next = null;
275         
276         /**
277          * Test results for this test object.
278          * @type object
279          * @property results
280          */                
281         this.results /*:Object*/ = {
282             passed : 0,
283             failed : 0,
284             total : 0,
285             ignored : 0,
286             duration: 0
287         };
288         
289         //initialize results
290         if (testObject instanceof YAHOO.tool.TestSuite){
291             this.results.type = "testsuite";
292             this.results.name = testObject.name;
293         } else if (testObject instanceof YAHOO.tool.TestCase){
294             this.results.type = "testcase";
295             this.results.name = testObject.name;
296         }
297        
298     }
299     
300     TestNode.prototype = {
301     
302         /**
303          * Appends a new test object (TestSuite, TestCase, or test function name) as a child
304          * of this node.
305          * @param {Variant} testObject A TestSuite, TestCase, or the name of a test function.
306          * @return {Void}
307          */
308         appendChild : function (testObject /*:Variant*/) /*:Void*/{
309             var node = new TestNode(testObject);
310             if (this.firstChild === null){
311                 this.firstChild = this.lastChild = node;
312             } else {
313                 this.lastChild.next = node;
314                 this.lastChild = node;
315             }
316             node.parent = this;
317             return node;
318         }       
319     };
320
321     /**
322      * Runs test suites and test cases, providing events to allowing for the
323      * interpretation of test results.
324      * @namespace YAHOO.tool
325      * @class TestRunner
326      * @static
327      */
328     function TestRunner(){
329     
330         //inherit from EventProvider
331         TestRunner.superclass.constructor.apply(this,arguments);
332         
333         /**
334          * Suite on which to attach all TestSuites and TestCases to be run.
335          * @type YAHOO.tool.TestSuite
336          * @property masterSuite
337          * @private
338          * @static
339          */
340         this.masterSuite = new YAHOO.tool.TestSuite("yuitests" + (new Date()).getTime());        
341
342         /**
343          * Pointer to the current node in the test tree.
344          * @type TestNode
345          * @private
346          * @property _cur
347          * @static
348          */
349         this._cur = null;                
350         
351         /**
352          * Pointer to the root node in the test tree.
353          * @type TestNode
354          * @private
355          * @property _root
356          * @static
357          */
358         this._root = null;
359         
360         /**
361          * Indicates if the TestRunner is currently running tests.
362          * @type Boolean
363          * @private
364          * @property _running
365          * @static
366          */
367         this._running = false;
368         
369         /**
370          * Holds copy of the results object generated when all tests are
371          * complete.
372          * @type Object
373          * @private
374          * @property _lastResults
375          * @static
376          */
377         this._lastResults = null;
378         
379         //create events
380         var events /*:Array*/ = [
381             this.TEST_CASE_BEGIN_EVENT,
382             this.TEST_CASE_COMPLETE_EVENT,
383             this.TEST_SUITE_BEGIN_EVENT,
384             this.TEST_SUITE_COMPLETE_EVENT,
385             this.TEST_PASS_EVENT,
386             this.TEST_FAIL_EVENT,
387             this.TEST_IGNORE_EVENT,
388             this.COMPLETE_EVENT,
389             this.BEGIN_EVENT
390         ];
391         for (var i=0; i < events.length; i++){
392             this.createEvent(events[i], { scope: this });
393         }       
394    
395     }
396     
397     YAHOO.lang.extend(TestRunner, YAHOO.util.EventProvider, {
398     
399         //-------------------------------------------------------------------------
400         // Constants
401         //-------------------------------------------------------------------------
402          
403         /**
404          * Fires when a test case is opened but before the first 
405          * test is executed.
406          * @event testcasebegin
407          */         
408         TEST_CASE_BEGIN_EVENT /*:String*/ : "testcasebegin",
409         
410         /**
411          * Fires when all tests in a test case have been executed.
412          * @event testcasecomplete
413          */        
414         TEST_CASE_COMPLETE_EVENT /*:String*/ : "testcasecomplete",
415         
416         /**
417          * Fires when a test suite is opened but before the first 
418          * test is executed.
419          * @event testsuitebegin
420          */        
421         TEST_SUITE_BEGIN_EVENT /*:String*/ : "testsuitebegin",
422         
423         /**
424          * Fires when all test cases in a test suite have been
425          * completed.
426          * @event testsuitecomplete
427          */        
428         TEST_SUITE_COMPLETE_EVENT /*:String*/ : "testsuitecomplete",
429         
430         /**
431          * Fires when a test has passed.
432          * @event pass
433          */        
434         TEST_PASS_EVENT /*:String*/ : "pass",
435         
436         /**
437          * Fires when a test has failed.
438          * @event fail
439          */        
440         TEST_FAIL_EVENT /*:String*/ : "fail",
441         
442         /**
443          * Fires when a test has been ignored.
444          * @event ignore
445          */        
446         TEST_IGNORE_EVENT /*:String*/ : "ignore",
447         
448         /**
449          * Fires when all test suites and test cases have been completed.
450          * @event complete
451          */        
452         COMPLETE_EVENT /*:String*/ : "complete",
453         
454         /**
455          * Fires when the run() method is called.
456          * @event begin
457          */        
458         BEGIN_EVENT /*:String*/ : "begin",                
459         
460         //-------------------------------------------------------------------------
461         // Misc Methods
462         //-------------------------------------------------------------------------   
463
464         /**
465          * Retrieves the name of the current result set.
466          * @return {String} The name of the result set.
467          * @method getName
468          */
469         getName: function(){
470             return this.masterSuite.name;
471         },         
472
473         /**
474          * The name assigned to the master suite of the TestRunner. This is the name
475          * that is output as the root's name when results are retrieved.
476          * @param {String} name The name of the result set.
477          * @return {Void}
478          * @method setName
479          */
480         setName: function(name){
481             this.masterSuite.name = name;
482         },        
483         
484         
485         //-------------------------------------------------------------------------
486         // State-Related Methods
487         //-------------------------------------------------------------------------
488
489         /**
490          * Indicates that the TestRunner is busy running tests and therefore can't
491          * be stopped and results cannot be gathered.
492          * @return {Boolean} True if the TestRunner is running, false if not.
493          * @method isRunning
494          */
495         isRunning: function(){
496             return this._running;
497         },
498         
499         /**
500          * Returns the last complete results set from the TestRunner. Null is returned
501          * if the TestRunner is running or no tests have been run.
502          * @param {Function} format (Optional) A test format to return the results in.
503          * @return {Object|String} Either the results object or, if a test format is 
504          *      passed as the argument, a string representing the results in a specific
505          *      format.
506          * @method getResults
507          */
508         getResults: function(format){
509             if (!this._running && this._lastResults){
510                 if (YAHOO.lang.isFunction(format)){
511                     return format(this._lastResults);                    
512                 } else {
513                     return this._lastResults;
514                 }
515             } else {
516                 return null;
517             }
518         },
519
520         /**
521          * Returns the coverage report for the files that have been executed.
522          * This returns only coverage information for files that have been
523          * instrumented using YUI Test Coverage and only those that were run
524          * in the same pass.
525          * @param {Function} format (Optional) A coverage format to return results in.
526          * @return {Object|String} Either the coverage object or, if a coverage
527          *      format is specified, a string representing the results in that format.
528          * @method getCoverage
529          */
530         getCoverage: function(format){
531             if (!this._running && typeof _yuitest_coverage == "object"){
532                 if (YAHOO.lang.isFunction(format)){
533                     return format(_yuitest_coverage);                    
534                 } else {
535                     return _yuitest_coverage;
536                 }
537             } else {
538                 return null;
539             }            
540         },
541         
542         //-------------------------------------------------------------------------
543         // Misc Methods
544         //-------------------------------------------------------------------------
545
546         /**
547          * Retrieves the name of the current result set.
548          * @return {String} The name of the result set.
549          * @method getName
550          */
551         getName: function(){
552             return this.masterSuite.name;
553         },         
554
555         /**
556          * The name assigned to the master suite of the TestRunner. This is the name
557          * that is output as the root's name when results are retrieved.
558          * @param {String} name The name of the result set.
559          * @return {Void}
560          * @method setName
561          */
562         setName: function(name){
563             this.masterSuite.name = name;
564         },
565
566         //-------------------------------------------------------------------------
567         // Test Tree-Related Methods
568         //-------------------------------------------------------------------------
569
570         /**
571          * Adds a test case to the test tree as a child of the specified node.
572          * @param {TestNode} parentNode The node to add the test case to as a child.
573          * @param {YAHOO.tool.TestCase} testCase The test case to add.
574          * @return {Void}
575          * @static
576          * @private
577          * @method _addTestCaseToTestTree
578          */
579        _addTestCaseToTestTree : function (parentNode /*:TestNode*/, testCase /*:YAHOO.tool.TestCase*/) /*:Void*/{
580             
581             //add the test suite
582             var node = parentNode.appendChild(testCase);
583             
584             //iterate over the items in the test case
585             for (var prop in testCase){
586                 if (prop.indexOf("test") === 0 && YAHOO.lang.isFunction(testCase[prop])){
587                     node.appendChild(prop);
588                 }
589             }
590          
591         },
592         
593         /**
594          * Adds a test suite to the test tree as a child of the specified node.
595          * @param {TestNode} parentNode The node to add the test suite to as a child.
596          * @param {YAHOO.tool.TestSuite} testSuite The test suite to add.
597          * @return {Void}
598          * @static
599          * @private
600          * @method _addTestSuiteToTestTree
601          */
602         _addTestSuiteToTestTree : function (parentNode /*:TestNode*/, testSuite /*:YAHOO.tool.TestSuite*/) /*:Void*/ {
603             
604             //add the test suite
605             var node = parentNode.appendChild(testSuite);
606             
607             //iterate over the items in the master suite
608             for (var i=0; i < testSuite.items.length; i++){
609                 if (testSuite.items[i] instanceof YAHOO.tool.TestSuite) {
610                     this._addTestSuiteToTestTree(node, testSuite.items[i]);
611                 } else if (testSuite.items[i] instanceof YAHOO.tool.TestCase) {
612                     this._addTestCaseToTestTree(node, testSuite.items[i]);
613                 }                   
614             }            
615         },
616         
617         /**
618          * Builds the test tree based on items in the master suite. The tree is a hierarchical
619          * representation of the test suites, test cases, and test functions. The resulting tree
620          * is stored in _root and the pointer _cur is set to the root initially.
621          * @return {Void}
622          * @static
623          * @private
624          * @method _buildTestTree
625          */
626         _buildTestTree : function () /*:Void*/ {
627         
628             this._root = new TestNode(this.masterSuite);
629             //this._cur = this._root;
630             
631             //iterate over the items in the master suite
632             for (var i=0; i < this.masterSuite.items.length; i++){
633                 if (this.masterSuite.items[i] instanceof YAHOO.tool.TestSuite) {
634                     this._addTestSuiteToTestTree(this._root, this.masterSuite.items[i]);
635                 } else if (this.masterSuite.items[i] instanceof YAHOO.tool.TestCase) {
636                     this._addTestCaseToTestTree(this._root, this.masterSuite.items[i]);
637                 }                   
638             }            
639         
640         }, 
641     
642         //-------------------------------------------------------------------------
643         // Private Methods
644         //-------------------------------------------------------------------------
645         
646         /**
647          * Handles the completion of a test object's tests. Tallies test results 
648          * from one level up to the next.
649          * @param {TestNode} node The TestNode representing the test object.
650          * @return {Void}
651          * @method _handleTestObjectComplete
652          * @private
653          * @static
654          */
655         _handleTestObjectComplete : function (node /*:TestNode*/) /*:Void*/ {
656             if (YAHOO.lang.isObject(node.testObject)){
657                 node.parent.results.passed += node.results.passed;
658                 node.parent.results.failed += node.results.failed;
659                 node.parent.results.total += node.results.total;                
660                 node.parent.results.ignored += node.results.ignored;                
661                 node.parent.results[node.testObject.name] = node.results;
662             
663                 if (node.testObject instanceof YAHOO.tool.TestSuite){
664                     node.testObject.tearDown();
665                     node.results.duration = (new Date()) - node._start;
666                     this.fireEvent(this.TEST_SUITE_COMPLETE_EVENT, { testSuite: node.testObject, results: node.results});
667                 } else if (node.testObject instanceof YAHOO.tool.TestCase){
668                     node.results.duration = (new Date()) - node._start;
669                     this.fireEvent(this.TEST_CASE_COMPLETE_EVENT, { testCase: node.testObject, results: node.results});
670                 }      
671             } 
672         },                
673         
674         //-------------------------------------------------------------------------
675         // Navigation Methods
676         //-------------------------------------------------------------------------
677         
678         /**
679          * Retrieves the next node in the test tree.
680          * @return {TestNode} The next node in the test tree or null if the end is reached.
681          * @private
682          * @static
683          * @method _next
684          */
685         _next : function () /*:TestNode*/ {
686                 
687             if (this._cur === null){
688                 this._cur = this._root;
689             } else if (this._cur.firstChild) {
690                 this._cur = this._cur.firstChild;
691             } else if (this._cur.next) {
692                 this._cur = this._cur.next;            
693             } else {
694                 while (this._cur && !this._cur.next && this._cur !== this._root){
695                     this._handleTestObjectComplete(this._cur);
696                     this._cur = this._cur.parent;
697                 }
698                 
699                 if (this._cur == this._root){
700                     this._cur.results.type = "report";
701                     this._cur.results.timestamp = (new Date()).toLocaleString();
702                     this._cur.results.duration = (new Date()) - this._cur._start;                       
703                     this._lastResults = this._cur.results;
704                     this._running = false;
705                     this.fireEvent(this.COMPLETE_EVENT, { results: this._lastResults});
706                     this._cur = null;
707                 } else {
708                     this._handleTestObjectComplete(this._cur);               
709                     this._cur = this._cur.next;                
710                 }
711             }
712         
713             return this._cur;
714         },
715         
716         /**
717          * Runs a test case or test suite, returning the results.
718          * @param {YAHOO.tool.TestCase|YAHOO.tool.TestSuite} testObject The test case or test suite to run.
719          * @return {Object} Results of the execution with properties passed, failed, and total.
720          * @private
721          * @method _run
722          * @static
723          */
724         _run : function () /*:Void*/ {
725                                 
726             //flag to indicate if the TestRunner should wait before continuing
727             var shouldWait = false;
728             
729             //get the next test node
730             var node = this._next();
731
732             
733             if (node !== null) {
734             
735                 //set flag to say the testrunner is running
736                 this._running = true;
737                 
738                 //eliminate last results
739                 this._lastResult = null;            
740             
741                 var testObject = node.testObject;
742                 
743                 //figure out what to do
744                 if (YAHOO.lang.isObject(testObject)){
745                     if (testObject instanceof YAHOO.tool.TestSuite){
746                         this.fireEvent(this.TEST_SUITE_BEGIN_EVENT, { testSuite: testObject });
747                         node._start = new Date();
748                         testObject.setUp();
749                     } else if (testObject instanceof YAHOO.tool.TestCase){
750                         this.fireEvent(this.TEST_CASE_BEGIN_EVENT, { testCase: testObject });
751                         node._start = new Date();
752                     }
753                     
754                     //some environments don't support setTimeout
755                     if (typeof setTimeout != "undefined"){                    
756                         setTimeout(function(){
757                             YAHOO.tool.TestRunner._run();
758                         }, 0);
759                     } else {
760                         this._run();
761                     }
762                 } else {
763                     this._runTest(node);
764                 }
765
766             }
767         },
768         
769         _resumeTest : function (segment /*:Function*/) /*:Void*/ {
770         
771             //get relevant information
772             var node /*:TestNode*/ = this._cur;
773             var testName /*:String*/ = node.testObject;
774             var testCase /*:YAHOO.tool.TestCase*/ = node.parent.testObject;
775             
776             //cancel other waits if available
777             if (testCase.__yui_wait){
778                 clearTimeout(testCase.__yui_wait);
779                 delete testCase.__yui_wait;
780             }            
781             
782             //get the "should" test cases
783             var shouldFail /*:Object*/ = (testCase._should.fail || {})[testName];
784             var shouldError /*:Object*/ = (testCase._should.error || {})[testName];
785             
786             //variable to hold whether or not the test failed
787             var failed /*:Boolean*/ = false;
788             var error /*:Error*/ = null;
789                 
790             //try the test
791             try {
792             
793                 //run the test
794                 segment.apply(testCase);
795                 
796                 //if it should fail, and it got here, then it's a fail because it didn't
797                 if (shouldFail){
798                     error = new YAHOO.util.ShouldFail();
799                     failed = true;
800                 } else if (shouldError){
801                     error = new YAHOO.util.ShouldError();
802                     failed = true;
803                 }
804                            
805             } catch (thrown /*:Error*/){
806                 if (thrown instanceof YAHOO.util.AssertionError) {
807                     if (!shouldFail){
808                         error = thrown;
809                         failed = true;
810                     }
811                 } else if (thrown instanceof YAHOO.tool.TestCase.Wait){
812                 
813                     if (YAHOO.lang.isFunction(thrown.segment)){
814                         if (YAHOO.lang.isNumber(thrown.delay)){
815                         
816                             //some environments don't support setTimeout
817                             if (typeof setTimeout != "undefined"){
818                                 testCase.__yui_wait = setTimeout(function(){
819                                     YAHOO.tool.TestRunner._resumeTest(thrown.segment);
820                                 }, thrown.delay);                             
821                             } else {
822                                 throw new Error("Asynchronous tests not supported in this environment.");
823                             }
824                         }
825                     }
826                     
827                     return;
828                 
829                 } else {
830                     //first check to see if it should error
831                     if (!shouldError) {                        
832                         error = new YAHOO.util.UnexpectedError(thrown);
833                         failed = true;
834                     } else {
835                         //check to see what type of data we have
836                         if (YAHOO.lang.isString(shouldError)){
837                             
838                             //if it's a string, check the error message
839                             if (thrown.message != shouldError){
840                                 error = new YAHOO.util.UnexpectedError(thrown);
841                                 failed = true;                                    
842                             }
843                         } else if (YAHOO.lang.isFunction(shouldError)){
844                         
845                             //if it's a function, see if the error is an instance of it
846                             if (!(thrown instanceof shouldError)){
847                                 error = new YAHOO.util.UnexpectedError(thrown);
848                                 failed = true;
849                             }
850                         
851                         } else if (YAHOO.lang.isObject(shouldError)){
852                         
853                             //if it's an object, check the instance and message
854                             if (!(thrown instanceof shouldError.constructor) || 
855                                     thrown.message != shouldError.message){
856                                 error = new YAHOO.util.UnexpectedError(thrown);
857                                 failed = true;                                    
858                             }
859                         
860                         }
861                     
862                     }
863                 }
864                 
865             }
866             
867             //fireEvent appropriate event
868             if (failed) {
869                 this.fireEvent(this.TEST_FAIL_EVENT, { testCase: testCase, testName: testName, error: error });
870             } else {
871                 this.fireEvent(this.TEST_PASS_EVENT, { testCase: testCase, testName: testName });
872             }
873             
874             //run the tear down
875             testCase.tearDown();
876         
877             //calculate duration
878             var duration = (new Date()) - node._start;            
879             
880             //update results
881             node.parent.results[testName] = { 
882                 result: failed ? "fail" : "pass",
883                 message: error ? error.getMessage() : "Test passed",
884                 type: "test",
885                 name: testName,
886                 duration: duration
887             };
888             
889             if (failed){
890                 node.parent.results.failed++;
891             } else {
892                 node.parent.results.passed++;
893             }
894             node.parent.results.total++;
895
896             //set timeout not supported in all environments
897             if (typeof setTimeout != "undefined"){
898                 setTimeout(function(){
899                     YAHOO.tool.TestRunner._run();
900                 }, 0);
901             } else {
902                 this._run();
903             }
904         
905         },
906                 
907         /**
908          * Runs a single test based on the data provided in the node.
909          * @param {TestNode} node The TestNode representing the test to run.
910          * @return {Void}
911          * @static
912          * @private
913          * @name _runTest
914          */
915         _runTest : function (node /*:TestNode*/) /*:Void*/ {
916         
917             //get relevant information
918             var testName /*:String*/ = node.testObject;
919             var testCase /*:YAHOO.tool.TestCase*/ = node.parent.testObject;
920             var test /*:Function*/ = testCase[testName];
921             
922             //get the "should" test cases
923             var shouldIgnore /*:Object*/ = (testCase._should.ignore || {})[testName];
924             
925             //figure out if the test should be ignored or not
926             if (shouldIgnore){
927             
928                 //update results
929                 node.parent.results[testName] = { 
930                     result: "ignore",
931                     message: "Test ignored",
932                     type: "test",
933                     name: testName
934                 };
935                 
936                 node.parent.results.ignored++;
937                 node.parent.results.total++;
938             
939                 this.fireEvent(this.TEST_IGNORE_EVENT, { testCase: testCase, testName: testName });
940                 
941                 //some environments don't support setTimeout
942                 if (typeof setTimeout != "undefined"){                    
943                     setTimeout(function(){
944                         YAHOO.tool.TestRunner._run();
945                     }, 0);              
946                 } else {
947                     this._run();
948                 }
949
950             } else {
951             
952                 //mark the start time
953                 node._start = new Date();
954             
955                 //run the setup
956                 testCase.setUp();
957                 
958                 //now call the body of the test
959                 this._resumeTest(test);                
960             }
961
962         },        
963         
964         //-------------------------------------------------------------------------
965         // Protected Methods
966         //-------------------------------------------------------------------------   
967     
968         /**
969          * Fires events for the TestRunner. This overrides the default fireEvent()
970          * method from EventProvider to add the type property to the data that is
971          * passed through on each event call.
972          * @param {String} type The type of event to fire.
973          * @param {Object} data (Optional) Data for the event.
974          * @method fireEvent
975          * @static
976          * @protected
977          */
978         fireEvent : function (type /*:String*/, data /*:Object*/) /*:Void*/ {
979             data = data || {};
980             data.type = type;
981             TestRunner.superclass.fireEvent.call(this, type, data);
982         },
983
984         //-------------------------------------------------------------------------
985         // Public Methods
986         //-------------------------------------------------------------------------   
987     
988         /**
989          * Adds a test suite or test case to the list of test objects to run.
990          * @param testObject Either a TestCase or a TestSuite that should be run.
991          * @return {Void}
992          * @method add
993          * @static
994          */
995         add : function (testObject /*:Object*/) /*:Void*/ {
996             this.masterSuite.add(testObject);
997         },
998         
999         /**
1000          * Removes all test objects from the runner.
1001          * @return {Void}
1002          * @method clear
1003          * @static
1004          */
1005         clear : function () /*:Void*/ {
1006             this.masterSuite = new YAHOO.tool.TestSuite("yuitests" + (new Date()).getTime());
1007         },
1008         
1009         /**
1010          * Resumes the TestRunner after wait() was called.
1011          * @param {Function} segment The function to run as the rest
1012          *      of the haulted test.
1013          * @return {Void}
1014          * @method resume
1015          * @static
1016          */
1017         resume : function (segment /*:Function*/) /*:Void*/ {
1018             this._resumeTest(segment || function(){});
1019         },
1020     
1021         /**
1022          * Runs the test suite.
1023          * @param {Boolean} oldMode (Optional) Specifies that the <= 2.8 way of
1024          *      internally managing test suites should be used.
1025          * @return {Void}
1026          * @method run
1027          * @static
1028          */
1029         run : function (oldMode) {
1030             
1031             //pointer to runner to avoid scope issues 
1032             var runner = YAHOO.tool.TestRunner;
1033             
1034             //if there's only one suite on the masterSuite, move it up
1035             if (!oldMode && this.masterSuite.items.length == 1 && this.masterSuite.items[0] instanceof YAHOO.tool.TestSuite){
1036                 this.masterSuite = this.masterSuite.items[0];
1037             }
1038
1039             //build the test tree
1040             runner._buildTestTree();
1041             
1042             //set when the test started
1043             runner._root._start = new Date();
1044                             
1045             //fire the begin event
1046             runner.fireEvent(runner.BEGIN_EVENT);
1047        
1048             //begin the testing
1049             runner._run();
1050         }    
1051     });
1052     
1053     return new TestRunner();
1054     
1055 })();
1056 YAHOO.namespace("util");
1057
1058 //-----------------------------------------------------------------------------
1059 // Assert object
1060 //-----------------------------------------------------------------------------
1061
1062 /**
1063  * The Assert object provides functions to test JavaScript values against
1064  * known and expected results. Whenever a comparison (assertion) fails,
1065  * an error is thrown.
1066  *
1067  * @namespace YAHOO.util
1068  * @class Assert
1069  * @static
1070  */
1071 YAHOO.util.Assert = {
1072
1073     //-------------------------------------------------------------------------
1074     // Helper Methods
1075     //-------------------------------------------------------------------------
1076     
1077     /**
1078      * Formats a message so that it can contain the original assertion message
1079      * in addition to the custom message.
1080      * @param {String} customMessage The message passed in by the developer.
1081      * @param {String} defaultMessage The message created by the error by default.
1082      * @return {String} The final error message, containing either or both.
1083      * @protected
1084      * @static
1085      * @method _formatMessage
1086      */
1087     _formatMessage : function (customMessage /*:String*/, defaultMessage /*:String*/) /*:String*/ {
1088         var message = customMessage;
1089         if (YAHOO.lang.isString(customMessage) && customMessage.length > 0){
1090             return YAHOO.lang.substitute(customMessage, { message: defaultMessage });
1091         } else {
1092             return defaultMessage;
1093         }        
1094     },
1095     
1096     //-------------------------------------------------------------------------
1097     // Generic Assertion Methods
1098     //-------------------------------------------------------------------------
1099     
1100     /** 
1101      * Forces an assertion error to occur.
1102      * @param {String} message (Optional) The message to display with the failure.
1103      * @method fail
1104      * @static
1105      */
1106     fail : function (message /*:String*/) /*:Void*/ {
1107         throw new YAHOO.util.AssertionError(this._formatMessage(message, "Test force-failed."));
1108     },       
1109     
1110     //-------------------------------------------------------------------------
1111     // Equality Assertion Methods
1112     //-------------------------------------------------------------------------    
1113     
1114     /**
1115      * Asserts that a value is equal to another. This uses the double equals sign
1116      * so type coercion may occur.
1117      * @param {Object} expected The expected value.
1118      * @param {Object} actual The actual value to test.
1119      * @param {String} message (Optional) The message to display if the assertion fails.
1120      * @method areEqual
1121      * @static
1122      */
1123     areEqual : function (expected /*:Object*/, actual /*:Object*/, message /*:String*/) /*:Void*/ {
1124         if (expected != actual) {
1125             throw new YAHOO.util.ComparisonFailure(this._formatMessage(message, "Values should be equal."), expected, actual);
1126         }
1127     },
1128     
1129     /**
1130      * Asserts that a value is not equal to another. This uses the double equals sign
1131      * so type coercion may occur.
1132      * @param {Object} unexpected The unexpected value.
1133      * @param {Object} actual The actual value to test.
1134      * @param {String} message (Optional) The message to display if the assertion fails.
1135      * @method areNotEqual
1136      * @static
1137      */
1138     areNotEqual : function (unexpected /*:Object*/, actual /*:Object*/, 
1139                          message /*:String*/) /*:Void*/ {
1140         if (unexpected == actual) {
1141             throw new YAHOO.util.UnexpectedValue(this._formatMessage(message, "Values should not be equal."), unexpected);
1142         }
1143     },
1144     
1145     /**
1146      * Asserts that a value is not the same as another. This uses the triple equals sign
1147      * so no type coercion may occur.
1148      * @param {Object} unexpected The unexpected value.
1149      * @param {Object} actual The actual value to test.
1150      * @param {String} message (Optional) The message to display if the assertion fails.
1151      * @method areNotSame
1152      * @static
1153      */
1154     areNotSame : function (unexpected /*:Object*/, actual /*:Object*/, message /*:String*/) /*:Void*/ {
1155         if (unexpected === actual) {
1156             throw new YAHOO.util.UnexpectedValue(this._formatMessage(message, "Values should not be the same."), unexpected);
1157         }
1158     },
1159
1160     /**
1161      * Asserts that a value is the same as another. This uses the triple equals sign
1162      * so no type coercion may occur.
1163      * @param {Object} expected The expected value.
1164      * @param {Object} actual The actual value to test.
1165      * @param {String} message (Optional) The message to display if the assertion fails.
1166      * @method areSame
1167      * @static
1168      */
1169     areSame : function (expected /*:Object*/, actual /*:Object*/, message /*:String*/) /*:Void*/ {
1170         if (expected !== actual) {
1171             throw new YAHOO.util.ComparisonFailure(this._formatMessage(message, "Values should be the same."), expected, actual);
1172         }
1173     },    
1174     
1175     //-------------------------------------------------------------------------
1176     // Boolean Assertion Methods
1177     //-------------------------------------------------------------------------    
1178     
1179     /**
1180      * Asserts that a value is false. This uses the triple equals sign
1181      * so no type coercion may occur.
1182      * @param {Object} actual The actual value to test.
1183      * @param {String} message (Optional) The message to display if the assertion fails.
1184      * @method isFalse
1185      * @static
1186      */
1187     isFalse : function (actual /*:Boolean*/, message /*:String*/) {
1188         if (false !== actual) {
1189             throw new YAHOO.util.ComparisonFailure(this._formatMessage(message, "Value should be false."), false, actual);
1190         }
1191     },
1192     
1193     /**
1194      * Asserts that a value is true. This uses the triple equals sign
1195      * so no type coercion may occur.
1196      * @param {Object} actual The actual value to test.
1197      * @param {String} message (Optional) The message to display if the assertion fails.
1198      * @method isTrue
1199      * @static
1200      */
1201     isTrue : function (actual /*:Boolean*/, message /*:String*/) /*:Void*/ {
1202         if (true !== actual) {
1203             throw new YAHOO.util.ComparisonFailure(this._formatMessage(message, "Value should be true."), true, actual);
1204         }
1205
1206     },
1207     
1208     //-------------------------------------------------------------------------
1209     // Special Value Assertion Methods
1210     //-------------------------------------------------------------------------    
1211     
1212     /**
1213      * Asserts that a value is not a number.
1214      * @param {Object} actual The value to test.
1215      * @param {String} message (Optional) The message to display if the assertion fails.
1216      * @method isNaN
1217      * @static
1218      */
1219     isNaN : function (actual /*:Object*/, message /*:String*/) /*:Void*/{
1220         if (!isNaN(actual)){
1221             throw new YAHOO.util.ComparisonFailure(this._formatMessage(message, "Value should be NaN."), NaN, actual);
1222         }    
1223     },
1224     
1225     /**
1226      * Asserts that a value is not the special NaN value.
1227      * @param {Object} actual The value to test.
1228      * @param {String} message (Optional) The message to display if the assertion fails.
1229      * @method isNotNaN
1230      * @static
1231      */
1232     isNotNaN : function (actual /*:Object*/, message /*:String*/) /*:Void*/{
1233         if (isNaN(actual)){
1234             throw new YAHOO.util.UnexpectedValue(this._formatMessage(message, "Values should not be NaN."), NaN);
1235         }    
1236     },
1237     
1238     /**
1239      * Asserts that a value is not null. This uses the triple equals sign
1240      * so no type coercion may occur.
1241      * @param {Object} actual The actual value to test.
1242      * @param {String} message (Optional) The message to display if the assertion fails.
1243      * @method isNotNull
1244      * @static
1245      */
1246     isNotNull : function (actual /*:Object*/, message /*:String*/) /*:Void*/ {
1247         if (YAHOO.lang.isNull(actual)) {
1248             throw new YAHOO.util.UnexpectedValue(this._formatMessage(message, "Values should not be null."), null);
1249         }
1250     },
1251
1252     /**
1253      * Asserts that a value is not undefined. This uses the triple equals sign
1254      * so no type coercion may occur.
1255      * @param {Object} actual The actual value to test.
1256      * @param {String} message (Optional) The message to display if the assertion fails.
1257      * @method isNotUndefined
1258      * @static
1259      */
1260     isNotUndefined : function (actual /*:Object*/, message /*:String*/) /*:Void*/ {
1261         if (YAHOO.lang.isUndefined(actual)) {
1262             throw new YAHOO.util.UnexpectedValue(this._formatMessage(message, "Value should not be undefined."), undefined);
1263         }
1264     },
1265
1266     /**
1267      * Asserts that a value is null. This uses the triple equals sign
1268      * so no type coercion may occur.
1269      * @param {Object} actual The actual value to test.
1270      * @param {String} message (Optional) The message to display if the assertion fails.
1271      * @method isNull
1272      * @static
1273      */
1274     isNull : function (actual /*:Object*/, message /*:String*/) /*:Void*/ {
1275         if (!YAHOO.lang.isNull(actual)) {
1276             throw new YAHOO.util.ComparisonFailure(this._formatMessage(message, "Value should be null."), null, actual);
1277         }
1278     },
1279         
1280     /**
1281      * Asserts that a value is undefined. This uses the triple equals sign
1282      * so no type coercion may occur.
1283      * @param {Object} actual The actual value to test.
1284      * @param {String} message (Optional) The message to display if the assertion fails.
1285      * @method isUndefined
1286      * @static
1287      */
1288     isUndefined : function (actual /*:Object*/, message /*:String*/) /*:Void*/ {
1289         if (!YAHOO.lang.isUndefined(actual)) {
1290             throw new YAHOO.util.ComparisonFailure(this._formatMessage(message, "Value should be undefined."), undefined, actual);
1291         }
1292     },    
1293     
1294     //--------------------------------------------------------------------------
1295     // Instance Assertion Methods
1296     //--------------------------------------------------------------------------    
1297    
1298     /**
1299      * Asserts that a value is an array.
1300      * @param {Object} actual The value to test.
1301      * @param {String} message (Optional) The message to display if the assertion fails.
1302      * @method isArray
1303      * @static
1304      */
1305     isArray : function (actual /*:Object*/, message /*:String*/) /*:Void*/ {
1306         if (!YAHOO.lang.isArray(actual)){
1307             throw new YAHOO.util.UnexpectedValue(this._formatMessage(message, "Value should be an array."), actual);
1308         }    
1309     },
1310    
1311     /**
1312      * Asserts that a value is a Boolean.
1313      * @param {Object} actual The value to test.
1314      * @param {String} message (Optional) The message to display if the assertion fails.
1315      * @method isBoolean
1316      * @static
1317      */
1318     isBoolean : function (actual /*:Object*/, message /*:String*/) /*:Void*/ {
1319         if (!YAHOO.lang.isBoolean(actual)){
1320             throw new YAHOO.util.UnexpectedValue(this._formatMessage(message, "Value should be a Boolean."), actual);
1321         }    
1322     },
1323    
1324     /**
1325      * Asserts that a value is a function.
1326      * @param {Object} actual The value to test.
1327      * @param {String} message (Optional) The message to display if the assertion fails.
1328      * @method isFunction
1329      * @static
1330      */
1331     isFunction : function (actual /*:Object*/, message /*:String*/) /*:Void*/ {
1332         if (!YAHOO.lang.isFunction(actual)){
1333             throw new YAHOO.util.UnexpectedValue(this._formatMessage(message, "Value should be a function."), actual);
1334         }    
1335     },
1336    
1337     /**
1338      * Asserts that a value is an instance of a particular object. This may return
1339      * incorrect results when comparing objects from one frame to constructors in
1340      * another frame. For best results, don't use in a cross-frame manner.
1341      * @param {Function} expected The function that the object should be an instance of.
1342      * @param {Object} actual The object to test.
1343      * @param {String} message (Optional) The message to display if the assertion fails.
1344      * @method isInstanceOf
1345      * @static
1346      */
1347     isInstanceOf : function (expected /*:Function*/, actual /*:Object*/, message /*:String*/) /*:Void*/ {
1348         if (!(actual instanceof expected)){
1349             throw new YAHOO.util.ComparisonFailure(this._formatMessage(message, "Value isn't an instance of expected type."), expected, actual);
1350         }
1351     },
1352     
1353     /**
1354      * Asserts that a value is a number.
1355      * @param {Object} actual The value to test.
1356      * @param {String} message (Optional) The message to display if the assertion fails.
1357      * @method isNumber
1358      * @static
1359      */
1360     isNumber : function (actual /*:Object*/, message /*:String*/) /*:Void*/ {
1361         if (!YAHOO.lang.isNumber(actual)){
1362             throw new YAHOO.util.UnexpectedValue(this._formatMessage(message, "Value should be a number."), actual);
1363         }    
1364     },    
1365     
1366     /**
1367      * Asserts that a value is an object.
1368      * @param {Object} actual The value to test.
1369      * @param {String} message (Optional) The message to display if the assertion fails.
1370      * @method isObject
1371      * @static
1372      */
1373     isObject : function (actual /*:Object*/, message /*:String*/) /*:Void*/ {
1374         if (!YAHOO.lang.isObject(actual)){
1375             throw new YAHOO.util.UnexpectedValue(this._formatMessage(message, "Value should be an object."), actual);
1376         }
1377     },
1378     
1379     /**
1380      * Asserts that a value is a string.
1381      * @param {Object} actual The value to test.
1382      * @param {String} message (Optional) The message to display if the assertion fails.
1383      * @method isString
1384      * @static
1385      */
1386     isString : function (actual /*:Object*/, message /*:String*/) /*:Void*/ {
1387         if (!YAHOO.lang.isString(actual)){
1388             throw new YAHOO.util.UnexpectedValue(this._formatMessage(message, "Value should be a string."), actual);
1389         }
1390     },
1391     
1392     /**
1393      * Asserts that a value is of a particular type. 
1394      * @param {String} expectedType The expected type of the variable.
1395      * @param {Object} actualValue The actual value to test.
1396      * @param {String} message (Optional) The message to display if the assertion fails.
1397      * @method isTypeOf
1398      * @static
1399      */
1400     isTypeOf : function (expected /*:String*/, actual /*:Object*/, message /*:String*/) /*:Void*/{
1401         if (typeof actual != expected){
1402             throw new YAHOO.util.ComparisonFailure(this._formatMessage(message, "Value should be of type " + expected + "."), expected, typeof actual);
1403         }
1404     }
1405 };
1406
1407 //-----------------------------------------------------------------------------
1408 // Assertion errors
1409 //-----------------------------------------------------------------------------
1410
1411 /**
1412  * AssertionError is thrown whenever an assertion fails. It provides methods
1413  * to more easily get at error information and also provides a base class
1414  * from which more specific assertion errors can be derived.
1415  *
1416  * @param {String} message The message to display when the error occurs.
1417  * @namespace YAHOO.util
1418  * @class AssertionError
1419  * @extends Error
1420  * @constructor
1421  */ 
1422 YAHOO.util.AssertionError = function (message /*:String*/){
1423
1424     //call superclass
1425     //arguments.callee.superclass.constructor.call(this, message);
1426     
1427     /*
1428      * Error message. Must be duplicated to ensure browser receives it.
1429      * @type String
1430      * @property message
1431      */
1432     this.message /*:String*/ = message;
1433     
1434     /**
1435      * The name of the error that occurred.
1436      * @type String
1437      * @property name
1438      */
1439     this.name /*:String*/ = "AssertionError";
1440 };
1441
1442 //inherit methods
1443 YAHOO.lang.extend(YAHOO.util.AssertionError, Object, {
1444
1445     /**
1446      * Returns a fully formatted error for an assertion failure. This should
1447      * be overridden by all subclasses to provide specific information.
1448      * @method getMessage
1449      * @return {String} A string describing the error.
1450      */
1451     getMessage : function () /*:String*/ {
1452         return this.message;
1453     },
1454     
1455     /**
1456      * Returns a string representation of the error.
1457      * @method toString
1458      * @return {String} A string representation of the error.
1459      */
1460     toString : function () /*:String*/ {
1461         return this.name + ": " + this.getMessage();
1462     }
1463     
1464 });
1465
1466 /**
1467  * ComparisonFailure is subclass of AssertionError that is thrown whenever
1468  * a comparison between two values fails. It provides mechanisms to retrieve
1469  * both the expected and actual value.
1470  *
1471  * @param {String} message The message to display when the error occurs.
1472  * @param {Object} expected The expected value.
1473  * @param {Object} actual The actual value that caused the assertion to fail.
1474  * @namespace YAHOO.util
1475  * @extends YAHOO.util.AssertionError
1476  * @class ComparisonFailure
1477  * @constructor
1478  */ 
1479 YAHOO.util.ComparisonFailure = function (message /*:String*/, expected /*:Object*/, actual /*:Object*/){
1480
1481     //call superclass
1482     YAHOO.util.AssertionError.call(this, message);
1483     
1484     /**
1485      * The expected value.
1486      * @type Object
1487      * @property expected
1488      */
1489     this.expected /*:Object*/ = expected;
1490     
1491     /**
1492      * The actual value.
1493      * @type Object
1494      * @property actual
1495      */
1496     this.actual /*:Object*/ = actual;
1497     
1498     /**
1499      * The name of the error that occurred.
1500      * @type String
1501      * @property name
1502      */
1503     this.name /*:String*/ = "ComparisonFailure";
1504     
1505 };
1506
1507 //inherit methods
1508 YAHOO.lang.extend(YAHOO.util.ComparisonFailure, YAHOO.util.AssertionError, {
1509
1510     /**
1511      * Returns a fully formatted error for an assertion failure. This message
1512      * provides information about the expected and actual values.
1513      * @method toString
1514      * @return {String} A string describing the error.
1515      */
1516     getMessage : function () /*:String*/ {
1517         return this.message + "\nExpected: " + this.expected + " (" + (typeof this.expected) + ")"  +
1518             "\nActual:" + this.actual + " (" + (typeof this.actual) + ")";
1519     }
1520
1521 });
1522
1523 /**
1524  * UnexpectedValue is subclass of AssertionError that is thrown whenever
1525  * a value was unexpected in its scope. This typically means that a test
1526  * was performed to determine that a value was *not* equal to a certain
1527  * value.
1528  *
1529  * @param {String} message The message to display when the error occurs.
1530  * @param {Object} unexpected The unexpected value.
1531  * @namespace YAHOO.util
1532  * @extends YAHOO.util.AssertionError
1533  * @class UnexpectedValue
1534  * @constructor
1535  */ 
1536 YAHOO.util.UnexpectedValue = function (message /*:String*/, unexpected /*:Object*/){
1537
1538     //call superclass
1539     YAHOO.util.AssertionError.call(this, message);
1540     
1541     /**
1542      * The unexpected value.
1543      * @type Object
1544      * @property unexpected
1545      */
1546     this.unexpected /*:Object*/ = unexpected;
1547     
1548     /**
1549      * The name of the error that occurred.
1550      * @type String
1551      * @property name
1552      */
1553     this.name /*:String*/ = "UnexpectedValue";
1554     
1555 };
1556
1557 //inherit methods
1558 YAHOO.lang.extend(YAHOO.util.UnexpectedValue, YAHOO.util.AssertionError, {
1559
1560     /**
1561      * Returns a fully formatted error for an assertion failure. The message
1562      * contains information about the unexpected value that was encountered.
1563      * @method getMessage
1564      * @return {String} A string describing the error.
1565      */
1566     getMessage : function () /*:String*/ {
1567         return this.message + "\nUnexpected: " + this.unexpected + " (" + (typeof this.unexpected) + ") ";
1568     }
1569
1570 });
1571
1572 /**
1573  * ShouldFail is subclass of AssertionError that is thrown whenever
1574  * a test was expected to fail but did not.
1575  *
1576  * @param {String} message The message to display when the error occurs.
1577  * @namespace YAHOO.util
1578  * @extends YAHOO.util.AssertionError
1579  * @class ShouldFail
1580  * @constructor
1581  */  
1582 YAHOO.util.ShouldFail = function (message /*:String*/){
1583
1584     //call superclass
1585     YAHOO.util.AssertionError.call(this, message || "This test should fail but didn't.");
1586     
1587     /**
1588      * The name of the error that occurred.
1589      * @type String
1590      * @property name
1591      */
1592     this.name /*:String*/ = "ShouldFail";
1593     
1594 };
1595
1596 //inherit methods
1597 YAHOO.lang.extend(YAHOO.util.ShouldFail, YAHOO.util.AssertionError);
1598
1599 /**
1600  * ShouldError is subclass of AssertionError that is thrown whenever
1601  * a test is expected to throw an error but doesn't.
1602  *
1603  * @param {String} message The message to display when the error occurs.
1604  * @namespace YAHOO.util
1605  * @extends YAHOO.util.AssertionError
1606  * @class ShouldError
1607  * @constructor
1608  */  
1609 YAHOO.util.ShouldError = function (message /*:String*/){
1610
1611     //call superclass
1612     YAHOO.util.AssertionError.call(this, message || "This test should have thrown an error but didn't.");
1613     
1614     /**
1615      * The name of the error that occurred.
1616      * @type String
1617      * @property name
1618      */
1619     this.name /*:String*/ = "ShouldError";
1620     
1621 };
1622
1623 //inherit methods
1624 YAHOO.lang.extend(YAHOO.util.ShouldError, YAHOO.util.AssertionError);
1625
1626 /**
1627  * UnexpectedError is subclass of AssertionError that is thrown whenever
1628  * an error occurs within the course of a test and the test was not expected
1629  * to throw an error.
1630  *
1631  * @param {Error} cause The unexpected error that caused this error to be 
1632  *                      thrown.
1633  * @namespace YAHOO.util
1634  * @extends YAHOO.util.AssertionError
1635  * @class UnexpectedError
1636  * @constructor
1637  */  
1638 YAHOO.util.UnexpectedError = function (cause /*:Object*/){
1639
1640     //call superclass
1641     YAHOO.util.AssertionError.call(this, "Unexpected error: " + cause.message);
1642     
1643     /**
1644      * The unexpected error that occurred.
1645      * @type Error
1646      * @property cause
1647      */
1648     this.cause /*:Error*/ = cause;
1649     
1650     /**
1651      * The name of the error that occurred.
1652      * @type String
1653      * @property name
1654      */
1655     this.name /*:String*/ = "UnexpectedError";
1656     
1657     /**
1658      * Stack information for the error (if provided).
1659      * @type String
1660      * @property stack
1661      */
1662     this.stack /*:String*/ = cause.stack;
1663     
1664 };
1665
1666 //inherit methods
1667 YAHOO.lang.extend(YAHOO.util.UnexpectedError, YAHOO.util.AssertionError);
1668 //-----------------------------------------------------------------------------
1669 // ArrayAssert object
1670 //-----------------------------------------------------------------------------
1671
1672 /**
1673  * The ArrayAssert object provides functions to test JavaScript array objects
1674  * for a variety of cases.
1675  *
1676  * @namespace YAHOO.util
1677  * @class ArrayAssert
1678  * @static
1679  */
1680  
1681 YAHOO.util.ArrayAssert = {
1682
1683     /**
1684      * Asserts that a value is present in an array. This uses the triple equals 
1685      * sign so no type coercion may occur.
1686      * @param {Object} needle The value that is expected in the array.
1687      * @param {Array} haystack An array of values.
1688      * @param {String} message (Optional) The message to display if the assertion fails.
1689      * @method contains
1690      * @static
1691      */
1692     contains : function (needle /*:Object*/, haystack /*:Array*/, 
1693                            message /*:String*/) /*:Void*/ {
1694         
1695         var found /*:Boolean*/ = false;
1696         var Assert = YAHOO.util.Assert;
1697         
1698         //begin checking values
1699         for (var i=0; i < haystack.length && !found; i++){
1700             if (haystack[i] === needle) {
1701                 found = true;
1702             }
1703         }
1704         
1705         if (!found){
1706             Assert.fail(Assert._formatMessage(message, "Value " + needle + " (" + (typeof needle) + ") not found in array [" + haystack + "]."));
1707         }
1708     },
1709
1710     /**
1711      * Asserts that a set of values are present in an array. This uses the triple equals 
1712      * sign so no type coercion may occur. For this assertion to pass, all values must
1713      * be found.
1714      * @param {Object[]} needles An array of values that are expected in the array.
1715      * @param {Array} haystack An array of values to check.
1716      * @param {String} message (Optional) The message to display if the assertion fails.
1717      * @method containsItems
1718      * @static
1719      */
1720     containsItems : function (needles /*:Object[]*/, haystack /*:Array*/, 
1721                            message /*:String*/) /*:Void*/ {
1722
1723         //begin checking values
1724         for (var i=0; i < needles.length; i++){
1725             this.contains(needles[i], haystack, message);
1726         }
1727     },
1728
1729     /**
1730      * Asserts that a value matching some condition is present in an array. This uses
1731      * a function to determine a match.
1732      * @param {Function} matcher A function that returns true if the items matches or false if not.
1733      * @param {Array} haystack An array of values.
1734      * @param {String} message (Optional) The message to display if the assertion fails.
1735      * @method containsMatch
1736      * @static
1737      */
1738     containsMatch : function (matcher /*:Function*/, haystack /*:Array*/, 
1739                            message /*:String*/) /*:Void*/ {
1740         
1741         //check for valid matcher
1742         if (typeof matcher != "function"){
1743             throw new TypeError("ArrayAssert.containsMatch(): First argument must be a function.");
1744         }
1745         
1746         var found /*:Boolean*/ = false;
1747         var Assert = YAHOO.util.Assert;
1748         
1749         //begin checking values
1750         for (var i=0; i < haystack.length && !found; i++){
1751             if (matcher(haystack[i])) {
1752                 found = true;
1753             }
1754         }
1755         
1756         if (!found){
1757             Assert.fail(Assert._formatMessage(message, "No match found in array [" + haystack + "]."));
1758         }
1759     },
1760
1761     /**
1762      * Asserts that a value is not present in an array. This uses the triple equals 
1763      * sign so no type coercion may occur.
1764      * @param {Object} needle The value that is expected in the array.
1765      * @param {Array} haystack An array of values.
1766      * @param {String} message (Optional) The message to display if the assertion fails.
1767      * @method doesNotContain
1768      * @static
1769      */
1770     doesNotContain : function (needle /*:Object*/, haystack /*:Array*/, 
1771                            message /*:String*/) /*:Void*/ {
1772         
1773         var found /*:Boolean*/ = false;
1774         var Assert = YAHOO.util.Assert;
1775         
1776         //begin checking values
1777         for (var i=0; i < haystack.length && !found; i++){
1778             if (haystack[i] === needle) {
1779                 found = true;
1780             }
1781         }
1782         
1783         if (found){
1784             Assert.fail(Assert._formatMessage(message, "Value found in array [" + haystack + "]."));
1785         }
1786     },
1787
1788     /**
1789      * Asserts that a set of values are not present in an array. This uses the triple equals 
1790      * sign so no type coercion may occur. For this assertion to pass, all values must
1791      * not be found.
1792      * @param {Object[]} needles An array of values that are not expected in the array.
1793      * @param {Array} haystack An array of values to check.
1794      * @param {String} message (Optional) The message to display if the assertion fails.
1795      * @method doesNotContainItems
1796      * @static
1797      */
1798     doesNotContainItems : function (needles /*:Object[]*/, haystack /*:Array*/, 
1799                            message /*:String*/) /*:Void*/ {
1800
1801         for (var i=0; i < needles.length; i++){
1802             this.doesNotContain(needles[i], haystack, message);
1803         }
1804
1805     },
1806         
1807     /**
1808      * Asserts that no values matching a condition are present in an array. This uses
1809      * a function to determine a match.
1810      * @param {Function} matcher A function that returns true if the items matches or false if not.
1811      * @param {Array} haystack An array of values.
1812      * @param {String} message (Optional) The message to display if the assertion fails.
1813      * @method doesNotContainMatch
1814      * @static
1815      */
1816     doesNotContainMatch : function (matcher /*:Function*/, haystack /*:Array*/, 
1817                            message /*:String*/) /*:Void*/ {
1818         
1819         //check for valid matcher
1820         if (typeof matcher != "function"){
1821             throw new TypeError("ArrayAssert.doesNotContainMatch(): First argument must be a function.");
1822         }
1823
1824         var found /*:Boolean*/ = false;
1825         var Assert = YAHOO.util.Assert;
1826         
1827         //begin checking values
1828         for (var i=0; i < haystack.length && !found; i++){
1829             if (matcher(haystack[i])) {
1830                 found = true;
1831             }
1832         }
1833         
1834         if (found){
1835             Assert.fail(Assert._formatMessage(message, "Value found in array [" + haystack + "]."));
1836         }
1837     },
1838         
1839     /**
1840      * Asserts that the given value is contained in an array at the specified index.
1841      * This uses the triple equals sign so no type coercion will occur.
1842      * @param {Object} needle The value to look for.
1843      * @param {Array} haystack The array to search in.
1844      * @param {int} index The index at which the value should exist.
1845      * @param {String} message (Optional) The message to display if the assertion fails.
1846      * @method indexOf
1847      * @static
1848      */
1849     indexOf : function (needle /*:Object*/, haystack /*:Array*/, index /*:int*/, message /*:String*/) /*:Void*/ {
1850     
1851         //try to find the value in the array
1852         for (var i=0; i < haystack.length; i++){
1853             if (haystack[i] === needle){
1854                 YAHOO.util.Assert.areEqual(index, i, message || "Value exists at index " + i + " but should be at index " + index + ".");
1855                 return;
1856             }
1857         }
1858         
1859         var Assert = YAHOO.util.Assert;
1860         
1861         //if it makes it here, it wasn't found at all
1862         Assert.fail(Assert._formatMessage(message, "Value doesn't exist in array [" + haystack + "]."));
1863     },
1864         
1865     /**
1866      * Asserts that the values in an array are equal, and in the same position,
1867      * as values in another array. This uses the double equals sign
1868      * so type coercion may occur. Note that the array objects themselves
1869      * need not be the same for this test to pass.
1870      * @param {Array} expected An array of the expected values.
1871      * @param {Array} actual Any array of the actual values.
1872      * @param {String} message (Optional) The message to display if the assertion fails.
1873      * @method itemsAreEqual
1874      * @static
1875      */
1876     itemsAreEqual : function (expected /*:Array*/, actual /*:Array*/, 
1877                            message /*:String*/) /*:Void*/ {
1878         
1879         //one may be longer than the other, so get the maximum length
1880         var len /*:int*/ = Math.max(expected.length, actual.length || 0);
1881         var Assert = YAHOO.util.Assert;
1882        
1883         //begin checking values
1884         for (var i=0; i < len; i++){
1885             Assert.areEqual(expected[i], actual[i], 
1886                 Assert._formatMessage(message, "Values in position " + i + " are not equal."));
1887         }
1888     },
1889     
1890     /**
1891      * Asserts that the values in an array are equivalent, and in the same position,
1892      * as values in another array. This uses a function to determine if the values
1893      * are equivalent. Note that the array objects themselves
1894      * need not be the same for this test to pass.
1895      * @param {Array} expected An array of the expected values.
1896      * @param {Array} actual Any array of the actual values.
1897      * @param {Function} comparator A function that returns true if the values are equivalent
1898      *      or false if not.
1899      * @param {String} message (Optional) The message to display if the assertion fails.
1900      * @return {Void}
1901      * @method itemsAreEquivalent
1902      * @static
1903      */
1904     itemsAreEquivalent : function (expected /*:Array*/, actual /*:Array*/, 
1905                            comparator /*:Function*/, message /*:String*/) /*:Void*/ {
1906         
1907         //make sure the comparator is valid
1908         if (typeof comparator != "function"){
1909             throw new TypeError("ArrayAssert.itemsAreEquivalent(): Third argument must be a function.");
1910         }
1911         
1912         //one may be longer than the other, so get the maximum length
1913         var len /*:int*/ = Math.max(expected.length, actual.length || 0);
1914         
1915         //begin checking values
1916         for (var i=0; i < len; i++){
1917             if (!comparator(expected[i], actual[i])){
1918                 throw new YAHOO.util.ComparisonFailure(YAHOO.util.Assert._formatMessage(message, "Values in position " + i + " are not equivalent."), expected[i], actual[i]);
1919             }
1920         }
1921     },
1922     
1923     /**
1924      * Asserts that an array is empty.
1925      * @param {Array} actual The array to test.
1926      * @param {String} message (Optional) The message to display if the assertion fails.
1927      * @method isEmpty
1928      * @static
1929      */
1930     isEmpty : function (actual /*:Array*/, message /*:String*/) /*:Void*/ {        
1931         if (actual.length > 0){
1932             var Assert = YAHOO.util.Assert;
1933             Assert.fail(Assert._formatMessage(message, "Array should be empty."));
1934         }
1935     },    
1936     
1937     /**
1938      * Asserts that an array is not empty.
1939      * @param {Array} actual The array to test.
1940      * @param {String} message (Optional) The message to display if the assertion fails.
1941      * @method isNotEmpty
1942      * @static
1943      */
1944     isNotEmpty : function (actual /*:Array*/, message /*:String*/) /*:Void*/ {        
1945         if (actual.length === 0){
1946             var Assert = YAHOO.util.Assert;
1947             Assert.fail(Assert._formatMessage(message, "Array should not be empty."));
1948         }
1949     },    
1950     
1951     /**
1952      * Asserts that the values in an array are the same, and in the same position,
1953      * as values in another array. This uses the triple equals sign
1954      * so no type coercion will occur. Note that the array objects themselves
1955      * need not be the same for this test to pass.
1956      * @param {Array} expected An array of the expected values.
1957      * @param {Array} actual Any array of the actual values.
1958      * @param {String} message (Optional) The message to display if the assertion fails.
1959      * @method itemsAreSame
1960      * @static
1961      */
1962     itemsAreSame : function (expected /*:Array*/, actual /*:Array*/, 
1963                           message /*:String*/) /*:Void*/ {
1964         
1965         //one may be longer than the other, so get the maximum length
1966         var len /*:int*/ = Math.max(expected.length, actual.length || 0);
1967         var Assert = YAHOO.util.Assert;
1968         
1969         //begin checking values
1970         for (var i=0; i < len; i++){
1971             Assert.areSame(expected[i], actual[i], 
1972                 Assert._formatMessage(message, "Values in position " + i + " are not the same."));
1973         }
1974     },
1975     
1976     /**
1977      * Asserts that the given value is contained in an array at the specified index,
1978      * starting from the back of the array.
1979      * This uses the triple equals sign so no type coercion will occur.
1980      * @param {Object} needle The value to look for.
1981      * @param {Array} haystack The array to search in.
1982      * @param {int} index The index at which the value should exist.
1983      * @param {String} message (Optional) The message to display if the assertion fails.
1984      * @method lastIndexOf
1985      * @static
1986      */
1987     lastIndexOf : function (needle /*:Object*/, haystack /*:Array*/, index /*:int*/, message /*:String*/) /*:Void*/ {
1988     
1989         var Assert = YAHOO.util.Assert;
1990     
1991         //try to find the value in the array
1992         for (var i=haystack.length; i >= 0; i--){
1993             if (haystack[i] === needle){
1994                 Assert.areEqual(index, i, Assert._formatMessage(message, "Value exists at index " + i + " but should be at index " + index + "."));
1995                 return;
1996             }
1997         }
1998         
1999         //if it makes it here, it wasn't found at all
2000         Assert.fail(Assert._formatMessage(message, "Value doesn't exist in array."));        
2001     }
2002     
2003 };
2004 YAHOO.namespace("util");
2005
2006
2007 //-----------------------------------------------------------------------------
2008 // ObjectAssert object
2009 //-----------------------------------------------------------------------------
2010
2011 /**
2012  * The ObjectAssert object provides functions to test JavaScript objects
2013  * for a variety of cases.
2014  *
2015  * @namespace YAHOO.util
2016  * @class ObjectAssert
2017  * @static
2018  */
2019 YAHOO.util.ObjectAssert = {
2020         
2021     /**
2022      * Asserts that all properties in the object exist in another object.
2023      * @param {Object} expected An object with the expected properties.
2024      * @param {Object} actual An object with the actual properties.
2025      * @param {String} message (Optional) The message to display if the assertion fails.
2026      * @method propertiesAreEqual
2027      * @static
2028      */
2029     propertiesAreEqual : function (expected /*:Object*/, actual /*:Object*/, 
2030                            message /*:String*/) /*:Void*/ {
2031         
2032         var Assert = YAHOO.util.Assert;
2033         
2034         //get all properties in the object
2035         var properties /*:Array*/ = [];        
2036         for (var property in expected){
2037             properties.push(property);
2038         }
2039         
2040         //see if the properties are in the expected object
2041         for (var i=0; i < properties.length; i++){
2042             Assert.isNotUndefined(actual[properties[i]], 
2043                 Assert._formatMessage(message, "Property '" + properties[i] + "' expected."));
2044         }
2045
2046     },
2047     
2048     /**
2049      * Asserts that an object has a property with the given name.
2050      * @param {String} propertyName The name of the property to test.
2051      * @param {Object} object The object to search.
2052      * @param {String} message (Optional) The message to display if the assertion fails.
2053      * @method hasProperty
2054      * @static
2055      */    
2056     hasProperty : function (propertyName /*:String*/, object /*:Object*/, message /*:String*/) /*:Void*/ {
2057         if (!(propertyName in object)){
2058             var Assert = YAHOO.util.Assert;
2059             Assert.fail(Assert._formatMessage(message, "Property '" + propertyName + "' not found on object."));
2060         }    
2061     },
2062     
2063     /**
2064      * Asserts that a property with the given name exists on an object instance (not on its prototype).
2065      * @param {String} propertyName The name of the property to test.
2066      * @param {Object} object The object to search.
2067      * @param {String} message (Optional) The message to display if the assertion fails.
2068      * @method hasProperty
2069      * @static
2070      */    
2071     hasOwnProperty : function (propertyName /*:String*/, object /*:Object*/, message /*:String*/) /*:Void*/ {
2072         if (!YAHOO.lang.hasOwnProperty(object, propertyName)){
2073             var Assert = YAHOO.util.Assert;
2074             Assert.fail(Assert._formatMessage(message, "Property '" + propertyName + "' not found on object instance."));
2075         }     
2076     }
2077 };
2078 //-----------------------------------------------------------------------------
2079 // DateAssert object
2080 //-----------------------------------------------------------------------------
2081
2082 /**
2083  * The DateAssert object provides functions to test JavaScript Date objects
2084  * for a variety of cases.
2085  *
2086  * @namespace YAHOO.util
2087  * @class DateAssert
2088  * @static
2089  */
2090  
2091 YAHOO.util.DateAssert = {
2092
2093     /**
2094      * Asserts that a date's month, day, and year are equal to another date's.
2095      * @param {Date} expected The expected date.
2096      * @param {Date} actual The actual date to test.
2097      * @param {String} message (Optional) The message to display if the assertion fails.
2098      * @method datesAreEqual
2099      * @static
2100      */
2101     datesAreEqual : function (expected /*:Date*/, actual /*:Date*/, message /*:String*/){
2102         if (expected instanceof Date && actual instanceof Date){
2103             var Assert = YAHOO.util.Assert;
2104             Assert.areEqual(expected.getFullYear(), actual.getFullYear(), Assert._formatMessage(message, "Years should be equal."));
2105             Assert.areEqual(expected.getMonth(), actual.getMonth(), Assert._formatMessage(message, "Months should be equal."));
2106             Assert.areEqual(expected.getDate(), actual.getDate(), Assert._formatMessage(message, "Day of month should be equal."));
2107         } else {
2108             throw new TypeError("DateAssert.datesAreEqual(): Expected and actual values must be Date objects.");
2109         }
2110     },
2111
2112     /**
2113      * Asserts that a date's hour, minutes, and seconds are equal to another date's.
2114      * @param {Date} expected The expected date.
2115      * @param {Date} actual The actual date to test.
2116      * @param {String} message (Optional) The message to display if the assertion fails.
2117      * @method timesAreEqual
2118      * @static
2119      */
2120     timesAreEqual : function (expected /*:Date*/, actual /*:Date*/, message /*:String*/){
2121         if (expected instanceof Date && actual instanceof Date){
2122             var Assert = YAHOO.util.Assert;
2123             Assert.areEqual(expected.getHours(), actual.getHours(), Assert._formatMessage(message, "Hours should be equal."));
2124             Assert.areEqual(expected.getMinutes(), actual.getMinutes(), Assert._formatMessage(message, "Minutes should be equal."));
2125             Assert.areEqual(expected.getSeconds(), actual.getSeconds(), Assert._formatMessage(message, "Seconds should be equal."));
2126         } else {
2127             throw new TypeError("DateAssert.timesAreEqual(): Expected and actual values must be Date objects.");
2128         }
2129     }
2130     
2131 };
2132 YAHOO.namespace("tool");
2133
2134 //-----------------------------------------------------------------------------
2135 // TestManager object
2136 //-----------------------------------------------------------------------------
2137
2138 /**
2139  * Runs pages containing test suite definitions.
2140  * @namespace YAHOO.tool
2141  * @class TestManager
2142  * @static
2143  */
2144 YAHOO.tool.TestManager = {
2145
2146     /**
2147      * Constant for the testpagebegin custom event
2148      * @property TEST_PAGE_BEGIN_EVENT
2149      * @static
2150      * @type string
2151      * @final
2152      */
2153     TEST_PAGE_BEGIN_EVENT /*:String*/ : "testpagebegin",
2154
2155     /**
2156      * Constant for the testpagecomplete custom event
2157      * @property TEST_PAGE_COMPLETE_EVENT
2158      * @static
2159      * @type string
2160      * @final
2161      */
2162     TEST_PAGE_COMPLETE_EVENT /*:String*/ : "testpagecomplete",
2163
2164     /**
2165      * Constant for the testmanagerbegin custom event
2166      * @property TEST_MANAGER_BEGIN_EVENT
2167      * @static
2168      * @type string
2169      * @final
2170      */
2171     TEST_MANAGER_BEGIN_EVENT /*:String*/ : "testmanagerbegin",
2172
2173     /**
2174      * Constant for the testmanagercomplete custom event
2175      * @property TEST_MANAGER_COMPLETE_EVENT
2176      * @static
2177      * @type string
2178      * @final
2179      */
2180     TEST_MANAGER_COMPLETE_EVENT /*:String*/ : "testmanagercomplete",
2181
2182     //-------------------------------------------------------------------------
2183     // Private Properties
2184     //-------------------------------------------------------------------------
2185     
2186     
2187     /**
2188      * The URL of the page currently being executed.
2189      * @type String
2190      * @private
2191      * @property _curPage
2192      * @static
2193      */
2194     _curPage /*:String*/ : null,
2195     
2196     /**
2197      * The frame used to load and run tests.
2198      * @type Window
2199      * @private
2200      * @property _frame
2201      * @static
2202      */
2203     _frame /*:Window*/ : null,
2204     
2205     /**
2206      * The logger used to output results from the various tests.
2207      * @type YAHOO.tool.TestLogger
2208      * @private
2209      * @property _logger
2210      * @static
2211      */
2212     _logger : null,
2213     
2214     /**
2215      * The timeout ID for the next iteration through the tests.
2216      * @type int
2217      * @private
2218      * @property _timeoutId
2219      * @static
2220      */
2221     _timeoutId /*:int*/ : 0,
2222     
2223     /**
2224      * Array of pages to load.
2225      * @type String[]
2226      * @private
2227      * @property _pages
2228      * @static
2229      */
2230     _pages /*:String[]*/ : [],
2231     
2232     /**
2233      * Aggregated results
2234      * @type Object
2235      * @private
2236      * @property _results
2237      * @static
2238      */
2239     _results: null,
2240     
2241     //-------------------------------------------------------------------------
2242     // Private Methods
2243     //-------------------------------------------------------------------------
2244     
2245     /**
2246      * Handles TestRunner.COMPLETE_EVENT, storing the results and beginning
2247      * the loop again.
2248      * @param {Object} data Data about the event.
2249      * @return {Void}
2250      * @private
2251      * @static
2252      */
2253     _handleTestRunnerComplete : function (data /*:Object*/) /*:Void*/ {
2254
2255         this.fireEvent(this.TEST_PAGE_COMPLETE_EVENT, {
2256                 page: this._curPage,
2257                 results: data.results
2258             });
2259     
2260         //save results
2261         //this._results[this.curPage] = data.results;
2262         
2263         //process 'em
2264         this._processResults(this._curPage, data.results);
2265         
2266         this._logger.clearTestRunner();
2267     
2268         //if there's more to do, set a timeout to begin again
2269         if (this._pages.length){
2270             this._timeoutId = setTimeout(function(){
2271                 YAHOO.tool.TestManager._run();
2272             }, 1000);
2273         } else {
2274             this.fireEvent(this.TEST_MANAGER_COMPLETE_EVENT, this._results);
2275         }
2276     },
2277     
2278     /**
2279      * Processes the results of a test page run, outputting log messages
2280      * for failed tests.
2281      * @return {Void}
2282      * @private
2283      * @static
2284      */
2285     _processResults : function (page /*:String*/, results /*:Object*/) /*:Void*/ {
2286
2287         var r = this._results;
2288         
2289         r.passed += results.passed;
2290         r.failed += results.failed;
2291         r.ignored += results.ignored;
2292         r.total += results.total;
2293         r.duration += results.duration;
2294         
2295         if (results.failed){
2296             r.failedPages.push(page);
2297         } else {
2298             r.passedPages.push(page);
2299         }
2300         
2301         results.name = page;
2302         results.type = "page";
2303         
2304         r[page] = results;
2305     },
2306     
2307     /**
2308      * Loads the next test page into the iframe.
2309      * @return {Void}
2310      * @static
2311      * @private
2312      */
2313     _run : function () /*:Void*/ {
2314     
2315         //set the current page
2316         this._curPage = this._pages.shift();
2317
2318         this.fireEvent(this.TEST_PAGE_BEGIN_EVENT, this._curPage);
2319         
2320         //load the frame - destroy history in case there are other iframes that
2321         //need testing
2322         this._frame.location.replace(this._curPage);
2323     
2324     },
2325         
2326     //-------------------------------------------------------------------------
2327     // Public Methods
2328     //-------------------------------------------------------------------------
2329     
2330     /**
2331      * Signals that a test page has been loaded. This should be called from
2332      * within the test page itself to notify the TestManager that it is ready.
2333      * @return {Void}
2334      * @static
2335      */
2336     load : function () /*:Void*/ {
2337         if (parent.YAHOO.tool.TestManager !== this){
2338             parent.YAHOO.tool.TestManager.load();
2339         } else {
2340             
2341             if (this._frame) {
2342                 //assign event handling
2343                 var TestRunner = this._frame.YAHOO.tool.TestRunner;
2344
2345                 this._logger.setTestRunner(TestRunner);
2346                 TestRunner.subscribe(TestRunner.COMPLETE_EVENT, this._handleTestRunnerComplete, this, true);
2347                 
2348                 //run it
2349                 TestRunner.run();
2350             }
2351         }
2352     },
2353     
2354     /**
2355      * Sets the pages to be loaded.
2356      * @param {String[]} pages An array of URLs to load.
2357      * @return {Void}
2358      * @static
2359      */
2360     setPages : function (pages /*:String[]*/) /*:Void*/ {
2361         this._pages = pages;
2362     },
2363     
2364     /**
2365      * Begins the process of running the tests.
2366      * @return {Void}
2367      * @static
2368      */
2369     start : function () /*:Void*/ {
2370
2371         if (!this._initialized) {
2372
2373             /**
2374              * Fires when loading a test page
2375              * @event testpagebegin
2376              * @param curPage {string} the page being loaded
2377              * @static
2378              */
2379             this.createEvent(this.TEST_PAGE_BEGIN_EVENT);
2380
2381             /**
2382              * Fires when a test page is complete
2383              * @event testpagecomplete
2384              * @param obj {page: string, results: object} the name of the
2385              * page that was loaded, and the test suite results
2386              * @static
2387              */
2388             this.createEvent(this.TEST_PAGE_COMPLETE_EVENT);
2389
2390             /**
2391              * Fires when the test manager starts running all test pages
2392              * @event testmanagerbegin
2393              * @static
2394              */
2395             this.createEvent(this.TEST_MANAGER_BEGIN_EVENT);
2396
2397             /**
2398              * Fires when the test manager finishes running all test pages.  External
2399              * test runners should subscribe to this event in order to get the
2400              * aggregated test results.
2401              * @event testmanagercomplete
2402              * @param obj { pages_passed: int, pages_failed: int, tests_passed: int
2403              *              tests_failed: int, passed: string[], failed: string[],
2404              *              page_results: {} }
2405              * @static
2406              */
2407             this.createEvent(this.TEST_MANAGER_COMPLETE_EVENT);
2408
2409             //create iframe if not already available
2410             if (!this._frame){
2411                 var frame /*:HTMLElement*/ = document.createElement("iframe");
2412                 frame.style.visibility = "hidden";
2413                 frame.style.position = "absolute";
2414                 document.body.appendChild(frame);
2415                 this._frame = frame.contentWindow || frame.contentDocument.parentWindow;
2416             }
2417             
2418             //create test logger if not already available
2419             if (!this._logger){
2420                 this._logger = new YAHOO.tool.TestLogger();
2421             }
2422
2423             this._initialized = true;
2424         }
2425
2426
2427         // reset the results cache
2428         this._results = {
2429         
2430             passed: 0,
2431             failed: 0,
2432             ignored: 0,
2433             total: 0,
2434             type: "report",
2435             name: "YUI Test Results",
2436             duration: 0,
2437             failedPages:[],
2438             passedPages:[]
2439             /*
2440             // number of pages that pass
2441             pages_passed: 0,
2442             // number of pages that fail
2443             pages_failed: 0,
2444             // total number of tests passed
2445             tests_passed: 0,
2446             // total number of tests failed
2447             tests_failed: 0,
2448             // array of pages that passed
2449             passed: [],
2450             // array of pages that failed
2451             failed: [],
2452             // map of full results for each page
2453             page_results: {}*/
2454         };
2455
2456         this.fireEvent(this.TEST_MANAGER_BEGIN_EVENT, null);
2457         this._run();
2458     
2459     },
2460
2461     /**
2462      * Stops the execution of tests.
2463      * @return {Void}
2464      * @static
2465      */
2466     stop : function () /*:Void*/ {
2467         clearTimeout(this._timeoutId);
2468     }
2469
2470 };
2471
2472 YAHOO.lang.augmentObject(YAHOO.tool.TestManager, YAHOO.util.EventProvider.prototype);
2473
2474 YAHOO.namespace("tool");
2475
2476 //-----------------------------------------------------------------------------
2477 // TestLogger object
2478 //-----------------------------------------------------------------------------
2479
2480 /**
2481  * Displays test execution progress and results, providing filters based on
2482  * different key events.
2483  * @namespace YAHOO.tool
2484  * @class TestLogger
2485  * @constructor
2486  * @param {HTMLElement} element (Optional) The element to create the logger in.
2487  * @param {Object} config (Optional) Configuration options for the logger.
2488  */
2489 YAHOO.tool.TestLogger = function (element, config) {
2490     YAHOO.tool.TestLogger.superclass.constructor.call(this, element, config);
2491     this.init();
2492 };
2493
2494 YAHOO.lang.extend(YAHOO.tool.TestLogger, YAHOO.widget.LogReader, {
2495
2496     footerEnabled : true,
2497     newestOnTop : false,
2498
2499     /**
2500      * Formats message string to HTML for output to console.
2501      * @private
2502      * @method formatMsg
2503      * @param oLogMsg {Object} Log message object.
2504      * @return {String} HTML-formatted message for output to console.
2505      */
2506     formatMsg : function(message /*:Object*/) {
2507     
2508         var category /*:String*/ = message.category;        
2509         var text /*:String*/ = this.html2Text(message.msg);
2510         
2511         return "<pre><p><span class=\"" + category + "\">" + category.toUpperCase() + "</span> " + text + "</p></pre>";
2512     
2513     },
2514     
2515     //-------------------------------------------------------------------------
2516     // Private Methods
2517     //-------------------------------------------------------------------------
2518     
2519     /*
2520      * Initializes the logger.
2521      * @private
2522      */
2523     init : function () {
2524     
2525         //attach to any available TestRunner
2526         if (YAHOO.tool.TestRunner){
2527             this.setTestRunner(YAHOO.tool.TestRunner);
2528         }
2529         
2530         //hide useless sources
2531         this.hideSource("global");
2532         this.hideSource("LogReader");
2533         
2534         //hide useless message categories
2535         this.hideCategory("warn");
2536         this.hideCategory("window");
2537         this.hideCategory("time");
2538         
2539         //reset the logger
2540         this.clearConsole();
2541     },
2542     
2543     /**
2544      * Clears the reference to the TestRunner from previous operations. This 
2545      * unsubscribes all events and removes the object reference.
2546      * @return {Void}
2547      * @static
2548      */
2549     clearTestRunner : function () /*:Void*/ {
2550         if (this._runner){
2551             this._runner.unsubscribeAll();
2552             this._runner = null;
2553         }
2554     },
2555     
2556     /**
2557      * Sets the source test runner that the logger should monitor.
2558      * @param {YAHOO.tool.TestRunner} testRunner The TestRunner to observe.
2559      * @return {Void}
2560      * @static
2561      */
2562     setTestRunner : function (testRunner /*:YAHOO.tool.TestRunner*/) /*:Void*/ {
2563     
2564         if (this._runner){
2565             this.clearTestRunner();
2566         }
2567         
2568         this._runner = testRunner;
2569         
2570         //setup event _handlers
2571         testRunner.subscribe(testRunner.TEST_PASS_EVENT, this._handleTestRunnerEvent, this, true);
2572         testRunner.subscribe(testRunner.TEST_FAIL_EVENT, this._handleTestRunnerEvent, this, true);
2573         testRunner.subscribe(testRunner.TEST_IGNORE_EVENT, this._handleTestRunnerEvent, this, true);
2574         testRunner.subscribe(testRunner.BEGIN_EVENT, this._handleTestRunnerEvent, this, true);
2575         testRunner.subscribe(testRunner.COMPLETE_EVENT, this._handleTestRunnerEvent, this, true);
2576         testRunner.subscribe(testRunner.TEST_SUITE_BEGIN_EVENT, this._handleTestRunnerEvent, this, true);
2577         testRunner.subscribe(testRunner.TEST_SUITE_COMPLETE_EVENT, this._handleTestRunnerEvent, this, true);
2578         testRunner.subscribe(testRunner.TEST_CASE_BEGIN_EVENT, this._handleTestRunnerEvent, this, true);
2579         testRunner.subscribe(testRunner.TEST_CASE_COMPLETE_EVENT, this._handleTestRunnerEvent, this, true);    
2580     },
2581     
2582     //-------------------------------------------------------------------------
2583     // Event Handlers
2584     //-------------------------------------------------------------------------
2585     
2586     /**
2587      * Handles all TestRunner events, outputting appropriate data into the console.
2588      * @param {Object} data The event data object.
2589      * @return {Void}
2590      * @private
2591      */
2592     _handleTestRunnerEvent : function (data /*:Object*/) /*:Void*/ {
2593     
2594         //shortcut variables
2595         var TestRunner /*:Object*/ = YAHOO.tool.TestRunner;
2596     
2597         //data variables
2598         var message /*:String*/ = "";
2599         var messageType /*:String*/ = "";
2600         
2601         switch(data.type){
2602             case TestRunner.BEGIN_EVENT:
2603                 message = "Testing began at " + (new Date()).toString() + ".";
2604                 messageType = "info";
2605                 break;
2606                 
2607             case TestRunner.COMPLETE_EVENT:
2608                 message = "Testing completed at " + (new Date()).toString() + ".\nPassed:" + 
2609                     data.results.passed + " Failed:" + data.results.failed + " Total:" + data.results.total;
2610                 messageType = "info";
2611                 break;
2612                 
2613             case TestRunner.TEST_FAIL_EVENT:
2614                 message = data.testName + ": " + data.error.getMessage();
2615                 messageType = "fail";
2616                 break;
2617                 
2618             case TestRunner.TEST_IGNORE_EVENT:
2619                 message = data.testName + ": ignored.";
2620                 messageType = "ignore";
2621                 break;
2622                 
2623             case TestRunner.TEST_PASS_EVENT:
2624                 message = data.testName + ": passed.";
2625                 messageType = "pass";
2626                 break;
2627                 
2628             case TestRunner.TEST_SUITE_BEGIN_EVENT:
2629                 message = "Test suite \"" + data.testSuite.name + "\" started.";
2630                 messageType = "info";
2631                 break;
2632                 
2633             case TestRunner.TEST_SUITE_COMPLETE_EVENT:
2634                 message = "Test suite \"" + data.testSuite.name + "\" completed.\nPassed:" + 
2635                     data.results.passed + " Failed:" + data.results.failed + " Total:" + data.results.total;
2636                 messageType = "info";
2637                 break;
2638                 
2639             case TestRunner.TEST_CASE_BEGIN_EVENT:
2640                 message = "Test case \"" + data.testCase.name + "\" started.";
2641                 messageType = "info";
2642                 break;
2643                 
2644             case TestRunner.TEST_CASE_COMPLETE_EVENT:
2645                 message = "Test case \"" + data.testCase.name + "\" completed.\nPassed:" + 
2646                     data.results.passed + " Failed:" + data.results.failed + " Total:" + data.results.total;
2647                 messageType = "info";
2648                 break;
2649             default:
2650                 message = "Unexpected event " + data.type;
2651                 message = "info";
2652         }
2653     
2654         YAHOO.log(message, messageType, "TestRunner");    
2655     }
2656     
2657 });
2658 YAHOO.namespace("tool.TestFormat");
2659
2660 (function(){
2661
2662     /**
2663      * Returns test results formatted as a JSON string. Requires JSON utility.
2664      * @param {Object} result The results object created by TestRunner.
2665      * @return {String} An XML-formatted string of results.
2666      * @namespace YAHOO.tool.TestFormat
2667      * @method JSON
2668      * @static
2669      */
2670     YAHOO.tool.TestFormat.JSON = function(results) {
2671         return YAHOO.lang.JSON.stringify(results);
2672     };
2673
2674     /* (intentionally not documented)
2675      * Simple escape function for XML attribute values.
2676      * @param {String} text The text to escape.
2677      * @return {String} The escaped text.
2678      */
2679     function xmlEscape(text){
2680         return text.replace(/["'<>&]/g, function(c){
2681             switch(c){
2682                 case "<":   return "&lt;";
2683                 case ">":   return "&gt;";
2684                 case "\"":  return "&quot;";
2685                 case "'":   return "&apos;";
2686                 case "&":   return "&amp;";
2687             }
2688         });
2689     } 
2690
2691     /**
2692      * Returns test results formatted as an XML string.
2693      * @param {Object} result The results object created by TestRunner.
2694      * @return {String} An XML-formatted string of results.
2695      * @namespace YAHOO.tool.TestFormat
2696      * @method XML
2697      * @static
2698      */
2699     YAHOO.tool.TestFormat.XML = function(results) {
2700
2701         function serializeToXML(results){
2702             var l   = YAHOO.lang,
2703                 xml = "<" + results.type + " name=\"" + xmlEscape(results.name) + "\"";
2704             
2705             if (l.isNumber(results.duration)){
2706                 xml += " duration=\"" + results.duration + "\"";
2707             }
2708             
2709             if (results.type == "test"){
2710                 xml += " result=\"" + results.result + "\" message=\"" + xmlEscape(results.message) + "\">";
2711             } else {
2712                 xml += " passed=\"" + results.passed + "\" failed=\"" + results.failed + "\" ignored=\"" + results.ignored + "\" total=\"" + results.total + "\">";
2713                 for (var prop in results) {
2714                     if (l.hasOwnProperty(results, prop) && l.isObject(results[prop]) && !l.isArray(results[prop])){
2715                         xml += serializeToXML(results[prop]);
2716                     }
2717                 }        
2718             }
2719
2720             xml += "</" + results.type + ">";
2721             
2722             return xml;    
2723         }
2724
2725         return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + serializeToXML(results);
2726
2727     };
2728
2729
2730     /**
2731      * Returns test results formatted in JUnit XML format.
2732      * @param {Object} result The results object created by TestRunner.
2733      * @return {String} An XML-formatted string of results.
2734      * @namespace YAHOO.tool.TestFormat
2735      * @method JUnitXML
2736      * @static
2737      */
2738     YAHOO.tool.TestFormat.JUnitXML = function(results) {
2739
2740
2741         function serializeToJUnitXML(results){
2742             var l   = YAHOO.lang,
2743                 xml = "",
2744                 prop;
2745                 
2746             switch (results.type){
2747                 //equivalent to testcase in JUnit
2748                 case "test":
2749                     if (results.result != "ignore"){
2750                         xml = "<testcase name=\"" + xmlEscape(results.name) + "\">";
2751                         if (results.result == "fail"){
2752                             xml += "<failure message=\"" + xmlEscape(results.message) + "\"><![CDATA[" + results.message + "]]></failure>";
2753                         }
2754                         xml+= "</testcase>";
2755                     }
2756                     break;
2757                     
2758                 //equivalent to testsuite in JUnit
2759                 case "testcase":
2760                 
2761                     xml = "<testsuite name=\"" + xmlEscape(results.name) + "\" tests=\"" + results.total + "\" failures=\"" + results.failed + "\">";
2762                 
2763                     for (prop in results) {
2764                         if (l.hasOwnProperty(results, prop) && l.isObject(results[prop]) && !l.isArray(results[prop])){
2765                             xml += serializeToJUnitXML(results[prop]);
2766                         }
2767                     }              
2768                     
2769                     xml += "</testsuite>";
2770                     break;
2771                 
2772                 case "testsuite":
2773                     for (prop in results) {
2774                         if (l.hasOwnProperty(results, prop) && l.isObject(results[prop]) && !l.isArray(results[prop])){
2775                             xml += serializeToJUnitXML(results[prop]);
2776                         }
2777                     } 
2778
2779                     //skip output - no JUnit equivalent                    
2780                     break;
2781                     
2782                 case "report":
2783                 
2784                     xml = "<testsuites>";
2785                 
2786                     for (prop in results) {
2787                         if (l.hasOwnProperty(results, prop) && l.isObject(results[prop]) && !l.isArray(results[prop])){
2788                             xml += serializeToJUnitXML(results[prop]);
2789                         }
2790                     }              
2791                     
2792                     xml += "</testsuites>";            
2793                 
2794                 //no default
2795             }
2796             
2797             return xml;
2798      
2799         }
2800
2801         return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + serializeToJUnitXML(results);
2802     };
2803     
2804     /**
2805      * Returns test results formatted in TAP format.
2806      * For more information, see <a href="http://testanything.org/">Test Anything Protocol</a>.
2807      * @param {Object} result The results object created by TestRunner.
2808      * @return {String} A TAP-formatted string of results.
2809      * @namespace YAHOO.tool.TestFormat
2810      * @method TAP
2811      * @static
2812      */
2813     YAHOO.tool.TestFormat.TAP = function(results) {
2814     
2815         var currentTestNum = 1;
2816
2817         function serializeToTAP(results){
2818             var l   = YAHOO.lang,
2819                 text = "";
2820                 
2821             switch (results.type){
2822
2823                 case "test":
2824                     if (results.result != "ignore"){
2825
2826                         text = "ok " + (currentTestNum++) + " - " + results.name;
2827                         
2828                         if (results.result == "fail"){
2829                             text = "not " + text + " - " + results.message;
2830                         }
2831                         
2832                         text += "\n";
2833                     } else {
2834                         text = "#Ignored test " + results.name + "\n";
2835                     }
2836                     break;
2837                     
2838                 case "testcase":
2839                 
2840                     text = "#Begin testcase " + results.name + "(" + results.failed + " failed of " + results.total + ")\n";
2841                                     
2842                                     
2843                     for (prop in results) {
2844                         if (l.hasOwnProperty(results, prop) && l.isObject(results[prop]) && !l.isArray(results[prop])){
2845                             text += serializeToTAP(results[prop]);
2846                         }
2847                     }              
2848                                                         
2849                     text += "#End testcase " + results.name + "\n";
2850                     
2851                     
2852                     break;
2853                 
2854                 case "testsuite":
2855
2856                     text = "#Begin testsuite " + results.name + "(" + results.failed + " failed of " + results.total + ")\n";                
2857                 
2858                     for (prop in results) {
2859                         if (l.hasOwnProperty(results, prop) && l.isObject(results[prop]) && !l.isArray(results[prop])){
2860                             text += serializeToTAP(results[prop]);
2861                         }
2862                     }   
2863
2864                     text += "#End testsuite " + results.name + "\n";
2865                     break;
2866
2867                 case "report":
2868                 
2869                     for (prop in results) {
2870                         if (l.hasOwnProperty(results, prop) && l.isObject(results[prop]) && !l.isArray(results[prop])){
2871                             text += serializeToTAP(results[prop]);
2872                         }
2873                     }             
2874                     
2875                 //no default
2876             }
2877             
2878             return text;
2879      
2880         }
2881
2882         return "1.." + results.total + "\n" + serializeToTAP(results);
2883     };   
2884
2885 })();
2886 YAHOO.namespace("tool.CoverageFormat");
2887
2888 /**
2889  * Returns the coverage report in JSON format. This is the straight
2890  * JSON representation of the native coverage report.
2891  * @param {Object} coverage The coverage report object.
2892  * @return {String} A JSON-formatted string of coverage data.
2893  * @method JSON
2894  * @namespace YAHOO.tool.CoverageFormat
2895  */
2896 YAHOO.tool.CoverageFormat.JSON = function(coverage){
2897     return YAHOO.lang.JSON.stringify(coverage);
2898 };
2899
2900 /**
2901  * Returns the coverage report in a JSON format compatible with
2902  * Xdebug. See <a href="http://www.xdebug.com/docs/code_coverage">Xdebug Documentation</a>
2903  * for more information. Note: function coverage is not available
2904  * in this format.
2905  * @param {Object} coverage The coverage report object.
2906  * @return {String} A JSON-formatted string of coverage data.
2907  * @method XdebugJSON
2908  * @namespace YAHOO.tool.CoverageFormat
2909  */
2910 YAHOO.tool.CoverageFormat.XdebugJSON = function(coverage){
2911     var report = {},
2912         prop;
2913     for (prop in coverage){
2914         if (coverage.hasOwnProperty(prop)){
2915             report[prop] = coverage[prop].lines;
2916         }
2917     }
2918
2919     return YAHOO.lang.JSON.stringify(report);        
2920 };
2921
2922 YAHOO.namespace("tool");
2923
2924 /**
2925  * An object capable of sending test results to a server.
2926  * @param {String} url The URL to submit the results to.
2927  * @param {Function} format (Optiona) A function that outputs the results in a specific format.
2928  *      Default is YAHOO.tool.TestFormat.XML.
2929  * @constructor
2930  * @namespace YAHOO.tool
2931  * @class TestReporter
2932  */
2933 YAHOO.tool.TestReporter = function(url /*:String*/, format /*:Function*/) {
2934
2935     /**
2936      * The URL to submit the data to.
2937      * @type String
2938      * @property url
2939      */
2940     this.url /*:String*/ = url;
2941
2942     /**
2943      * The formatting function to call when submitting the data.
2944      * @type Function
2945      * @property format
2946      */
2947     this.format /*:Function*/ = format || YAHOO.tool.TestFormat.XML;
2948
2949     /**
2950      * Extra fields to submit with the request.
2951      * @type Object
2952      * @property _fields
2953      * @private
2954      */
2955     this._fields /*:Object*/ = new Object();
2956     
2957     /**
2958      * The form element used to submit the results.
2959      * @type HTMLFormElement
2960      * @property _form
2961      * @private
2962      */
2963     this._form /*:HTMLElement*/ = null;
2964
2965     /**
2966      * Iframe used as a target for form submission.
2967      * @type HTMLIFrameElement
2968      * @property _iframe
2969      * @private
2970      */
2971     this._iframe /*:HTMLElement*/ = null;
2972 };
2973
2974 YAHOO.tool.TestReporter.prototype = {
2975
2976     //restore missing constructor
2977     constructor: YAHOO.tool.TestReporter,
2978     
2979     /**
2980      * Convert a date into ISO format.
2981      * From Douglas Crockford's json2.js
2982      * @param {Date} date The date to convert.
2983      * @return {String} An ISO-formatted date string
2984      * @method _convertToISOString
2985      * @private
2986      */    
2987     _convertToISOString: function(date){
2988         function f(n) {
2989             // Format integers to have at least two digits.
2990             return n < 10 ? '0' + n : n;
2991         }
2992
2993         return date.getUTCFullYear()   + '-' +
2994              f(date.getUTCMonth() + 1) + '-' +
2995              f(date.getUTCDate())      + 'T' +
2996              f(date.getUTCHours())     + ':' +
2997              f(date.getUTCMinutes())   + ':' +
2998              f(date.getUTCSeconds())   + 'Z';     
2999     
3000     },
3001
3002     /**
3003      * Adds a field to the form that submits the results.
3004      * @param {String} name The name of the field.
3005      * @param {Variant} value The value of the field.
3006      * @return {Void}
3007      * @method addField
3008      */
3009     addField : function (name /*:String*/, value /*:Variant*/) /*:Void*/{
3010         this._fields[name] = value;    
3011     },
3012     
3013     /**
3014      * Removes all previous defined fields.
3015      * @return {Void}
3016      * @method addField
3017      */
3018     clearFields : function() /*:Void*/{
3019         this._fields = new Object();
3020     },
3021
3022     /**
3023      * Cleans up the memory associated with the TestReporter, removing DOM elements
3024      * that were created.
3025      * @return {Void}
3026      * @method destroy
3027      */
3028     destroy : function() /*:Void*/ {
3029         if (this._form){
3030             this._form.parentNode.removeChild(this._form);
3031             this._form = null;
3032         }        
3033         if (this._iframe){
3034             this._iframe.parentNode.removeChild(this._iframe);
3035             this._iframe = null;
3036         }
3037         this._fields = null;
3038     },
3039
3040     /**
3041      * Sends the report to the server.
3042      * @param {Object} results The results object created by TestRunner.
3043      * @return {Void}
3044      * @method report
3045      */
3046     report : function(results /*:Object*/) /*:Void*/{
3047     
3048         //if the form hasn't been created yet, create it
3049         if (!this._form){
3050             this._form = document.createElement("form");
3051             this._form.method = "post";
3052             this._form.style.visibility = "hidden";
3053             this._form.style.position = "absolute";
3054             this._form.style.top = 0;
3055             document.body.appendChild(this._form);
3056         
3057             //IE won't let you assign a name using the DOM, must do it the hacky way
3058             if (YAHOO.env.ua.ie){
3059                 this._iframe = document.createElement("<iframe name=\"yuiTestTarget\" />");
3060             } else {
3061                 this._iframe = document.createElement("iframe");
3062                 this._iframe.name = "yuiTestTarget";
3063             }
3064
3065             this._iframe.src = "javascript:false";
3066             this._iframe.style.visibility = "hidden";
3067             this._iframe.style.position = "absolute";
3068             this._iframe.style.top = 0;
3069             document.body.appendChild(this._iframe);
3070
3071             this._form.target = "yuiTestTarget";
3072         }
3073
3074         //set the form's action
3075         this._form.action = this.url;
3076     
3077         //remove any existing fields
3078         while(this._form.hasChildNodes()){
3079             this._form.removeChild(this._form.lastChild);
3080         }
3081         
3082         //create default fields
3083         this._fields.results = this.format(results);
3084         this._fields.useragent = navigator.userAgent;
3085         this._fields.timestamp = this._convertToISOString(new Date());
3086
3087         //add fields to the form
3088         for (var prop in this._fields){
3089             if (YAHOO.lang.hasOwnProperty(this._fields, prop) && typeof this._fields[prop] != "function"){
3090                 if (YAHOO.env.ua.ie){
3091                     input = document.createElement("<input name=\"" + prop + "\" >");
3092                 } else {
3093                     input = document.createElement("input");
3094                     input.name = prop;
3095                 }
3096                 input.type = "hidden";
3097                 input.value = this._fields[prop];
3098                 this._form.appendChild(input);
3099             }
3100         }
3101
3102         //remove default fields
3103         delete this._fields.results;
3104         delete this._fields.useragent;
3105         delete this._fields.timestamp;
3106         
3107         if (arguments[1] !== false){
3108             this._form.submit();
3109         }
3110     
3111     }
3112
3113 };
3114 /*Stub for future compatibility*/
3115 YUITest = {
3116     TestRunner:     YAHOO.tool.TestRunner,
3117     ResultsFormat:  YAHOO.tool.TestFormat,
3118     CoverageFormat: YAHOO.tool.CoverageFormat
3119 };
3120 YAHOO.register("yuitest", YAHOO.tool.TestRunner, {version: "2.9.0", build: "2800"});