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