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