922a417a58950cc82155af6fe2ceb7006a0178ae
[yaffs-website] / jasmine / jasmine.js
1 var isCommonJS = typeof window == "undefined";
2
3 /**
4  * Top level namespace for Jasmine, a lightweight JavaScript BDD/spec/testing framework.
5  *
6  * @namespace
7  */
8 var jasmine = {};
9 if (isCommonJS) exports.jasmine = jasmine;
10 /**
11  * @private
12  */
13 jasmine.unimplementedMethod_ = function() {
14   throw new Error("unimplemented method");
15 };
16
17 /**
18  * Use <code>jasmine.undefined</code> instead of <code>undefined</code>, since <code>undefined</code> is just
19  * a plain old variable and may be redefined by somebody else.
20  *
21  * @private
22  */
23 jasmine.undefined = jasmine.___undefined___;
24
25 /**
26  * Show diagnostic messages in the console if set to true
27  *
28  */
29 jasmine.VERBOSE = false;
30
31 /**
32  * Default interval in milliseconds for event loop yields (e.g. to allow network activity or to refresh the screen with the HTML-based runner). Small values here may result in slow test running. Zero means no updates until all tests have completed.
33  *
34  */
35 jasmine.DEFAULT_UPDATE_INTERVAL = 250;
36
37 /**
38  * Default timeout interval in milliseconds for waitsFor() blocks.
39  */
40 jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000;
41
42 jasmine.getGlobal = function() {
43   function getGlobal() {
44     return this;
45   }
46
47   return getGlobal();
48 };
49
50 /**
51  * Allows for bound functions to be compared.  Internal use only.
52  *
53  * @ignore
54  * @private
55  * @param base {Object} bound 'this' for the function
56  * @param name {Function} function to find
57  */
58 jasmine.bindOriginal_ = function(base, name) {
59   var original = base[name];
60   if (original.apply) {
61     return function() {
62       return original.apply(base, arguments);
63     };
64   } else {
65     // IE support
66     return jasmine.getGlobal()[name];
67   }
68 };
69
70 jasmine.setTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'setTimeout');
71 jasmine.clearTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearTimeout');
72 jasmine.setInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'setInterval');
73 jasmine.clearInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearInterval');
74
75 jasmine.MessageResult = function(values) {
76   this.type = 'log';
77   this.values = values;
78   this.trace = new Error(); // todo: test better
79 };
80
81 jasmine.MessageResult.prototype.toString = function() {
82   var text = "";
83   for (var i = 0; i < this.values.length; i++) {
84     if (i > 0) text += " ";
85     if (jasmine.isString_(this.values[i])) {
86       text += this.values[i];
87     } else {
88       text += jasmine.pp(this.values[i]);
89     }
90   }
91   return text;
92 };
93
94 jasmine.ExpectationResult = function(params) {
95   this.type = 'expect';
96   this.matcherName = params.matcherName;
97   this.passed_ = params.passed;
98   this.expected = params.expected;
99   this.actual = params.actual;
100   this.message = this.passed_ ? 'Passed.' : params.message;
101
102   var trace = (params.trace || new Error(this.message));
103   this.trace = this.passed_ ? '' : trace;
104 };
105
106 jasmine.ExpectationResult.prototype.toString = function () {
107   return this.message;
108 };
109
110 jasmine.ExpectationResult.prototype.passed = function () {
111   return this.passed_;
112 };
113
114 /**
115  * Getter for the Jasmine environment. Ensures one gets created
116  */
117 jasmine.getEnv = function() {
118   var env = jasmine.currentEnv_ = jasmine.currentEnv_ || new jasmine.Env();
119   return env;
120 };
121
122 /**
123  * @ignore
124  * @private
125  * @param value
126  * @returns {Boolean}
127  */
128 jasmine.isArray_ = function(value) {
129   return jasmine.isA_("Array", value);
130 };
131
132 /**
133  * @ignore
134  * @private
135  * @param value
136  * @returns {Boolean}
137  */
138 jasmine.isString_ = function(value) {
139   return jasmine.isA_("String", value);
140 };
141
142 /**
143  * @ignore
144  * @private
145  * @param value
146  * @returns {Boolean}
147  */
148 jasmine.isNumber_ = function(value) {
149   return jasmine.isA_("Number", value);
150 };
151
152 /**
153  * @ignore
154  * @private
155  * @param {String} typeName
156  * @param value
157  * @returns {Boolean}
158  */
159 jasmine.isA_ = function(typeName, value) {
160   return Object.prototype.toString.apply(value) === '[object ' + typeName + ']';
161 };
162
163 /**
164  * Pretty printer for expecations.  Takes any object and turns it into a human-readable string.
165  *
166  * @param value {Object} an object to be outputted
167  * @returns {String}
168  */
169 jasmine.pp = function(value) {
170   var stringPrettyPrinter = new jasmine.StringPrettyPrinter();
171   stringPrettyPrinter.format(value);
172   return stringPrettyPrinter.string;
173 };
174
175 /**
176  * Returns true if the object is a DOM Node.
177  *
178  * @param {Object} obj object to check
179  * @returns {Boolean}
180  */
181 jasmine.isDomNode = function(obj) {
182   return obj.nodeType > 0;
183 };
184
185 /**
186  * Returns a matchable 'generic' object of the class type.  For use in expecations of type when values don't matter.
187  *
188  * @example
189  * // don't care about which function is passed in, as long as it's a function
190  * expect(mySpy).toHaveBeenCalledWith(jasmine.any(Function));
191  *
192  * @param {Class} clazz
193  * @returns matchable object of the type clazz
194  */
195 jasmine.any = function(clazz) {
196   return new jasmine.Matchers.Any(clazz);
197 };
198
199 /**
200  * Jasmine Spies are test doubles that can act as stubs, spies, fakes or when used in an expecation, mocks.
201  *
202  * Spies should be created in test setup, before expectations.  They can then be checked, using the standard Jasmine
203  * expectation syntax. Spies can be checked if they were called or not and what the calling params were.
204  *
205  * A Spy has the following fields: wasCalled, callCount, mostRecentCall, and argsForCall (see docs).
206  *
207  * Spies are torn down at the end of every spec.
208  *
209  * Note: Do <b>not</b> call new jasmine.Spy() directly - a spy must be created using spyOn, jasmine.createSpy or jasmine.createSpyObj.
210  *
211  * @example
212  * // a stub
213  * var myStub = jasmine.createSpy('myStub');  // can be used anywhere
214  *
215  * // spy example
216  * var foo = {
217  *   not: function(bool) { return !bool; }
218  * }
219  *
220  * // actual foo.not will not be called, execution stops
221  * spyOn(foo, 'not');
222
223  // foo.not spied upon, execution will continue to implementation
224  * spyOn(foo, 'not').andCallThrough();
225  *
226  * // fake example
227  * var foo = {
228  *   not: function(bool) { return !bool; }
229  * }
230  *
231  * // foo.not(val) will return val
232  * spyOn(foo, 'not').andCallFake(function(value) {return value;});
233  *
234  * // mock example
235  * foo.not(7 == 7);
236  * expect(foo.not).toHaveBeenCalled();
237  * expect(foo.not).toHaveBeenCalledWith(true);
238  *
239  * @constructor
240  * @see spyOn, jasmine.createSpy, jasmine.createSpyObj
241  * @param {String} name
242  */
243 jasmine.Spy = function(name) {
244   /**
245    * The name of the spy, if provided.
246    */
247   this.identity = name || 'unknown';
248   /**
249    *  Is this Object a spy?
250    */
251   this.isSpy = true;
252   /**
253    * The actual function this spy stubs.
254    */
255   this.plan = function() {
256   };
257   /**
258    * Tracking of the most recent call to the spy.
259    * @example
260    * var mySpy = jasmine.createSpy('foo');
261    * mySpy(1, 2);
262    * mySpy.mostRecentCall.args = [1, 2];
263    */
264   this.mostRecentCall = {};
265
266   /**
267    * Holds arguments for each call to the spy, indexed by call count
268    * @example
269    * var mySpy = jasmine.createSpy('foo');
270    * mySpy(1, 2);
271    * mySpy(7, 8);
272    * mySpy.mostRecentCall.args = [7, 8];
273    * mySpy.argsForCall[0] = [1, 2];
274    * mySpy.argsForCall[1] = [7, 8];
275    */
276   this.argsForCall = [];
277   this.calls = [];
278 };
279
280 /**
281  * Tells a spy to call through to the actual implemenatation.
282  *
283  * @example
284  * var foo = {
285  *   bar: function() { // do some stuff }
286  * }
287  *
288  * // defining a spy on an existing property: foo.bar
289  * spyOn(foo, 'bar').andCallThrough();
290  */
291 jasmine.Spy.prototype.andCallThrough = function() {
292   this.plan = this.originalValue;
293   return this;
294 };
295
296 /**
297  * For setting the return value of a spy.
298  *
299  * @example
300  * // defining a spy from scratch: foo() returns 'baz'
301  * var foo = jasmine.createSpy('spy on foo').andReturn('baz');
302  *
303  * // defining a spy on an existing property: foo.bar() returns 'baz'
304  * spyOn(foo, 'bar').andReturn('baz');
305  *
306  * @param {Object} value
307  */
308 jasmine.Spy.prototype.andReturn = function(value) {
309   this.plan = function() {
310     return value;
311   };
312   return this;
313 };
314
315 /**
316  * For throwing an exception when a spy is called.
317  *
318  * @example
319  * // defining a spy from scratch: foo() throws an exception w/ message 'ouch'
320  * var foo = jasmine.createSpy('spy on foo').andThrow('baz');
321  *
322  * // defining a spy on an existing property: foo.bar() throws an exception w/ message 'ouch'
323  * spyOn(foo, 'bar').andThrow('baz');
324  *
325  * @param {String} exceptionMsg
326  */
327 jasmine.Spy.prototype.andThrow = function(exceptionMsg) {
328   this.plan = function() {
329     throw exceptionMsg;
330   };
331   return this;
332 };
333
334 /**
335  * Calls an alternate implementation when a spy is called.
336  *
337  * @example
338  * var baz = function() {
339  *   // do some stuff, return something
340  * }
341  * // defining a spy from scratch: foo() calls the function baz
342  * var foo = jasmine.createSpy('spy on foo').andCall(baz);
343  *
344  * // defining a spy on an existing property: foo.bar() calls an anonymnous function
345  * spyOn(foo, 'bar').andCall(function() { return 'baz';} );
346  *
347  * @param {Function} fakeFunc
348  */
349 jasmine.Spy.prototype.andCallFake = function(fakeFunc) {
350   this.plan = fakeFunc;
351   return this;
352 };
353
354 /**
355  * Resets all of a spy's the tracking variables so that it can be used again.
356  *
357  * @example
358  * spyOn(foo, 'bar');
359  *
360  * foo.bar();
361  *
362  * expect(foo.bar.callCount).toEqual(1);
363  *
364  * foo.bar.reset();
365  *
366  * expect(foo.bar.callCount).toEqual(0);
367  */
368 jasmine.Spy.prototype.reset = function() {
369   this.wasCalled = false;
370   this.callCount = 0;
371   this.argsForCall = [];
372   this.calls = [];
373   this.mostRecentCall = {};
374 };
375
376 jasmine.createSpy = function(name) {
377
378   var spyObj = function() {
379     spyObj.wasCalled = true;
380     spyObj.callCount++;
381     var args = jasmine.util.argsToArray(arguments);
382     spyObj.mostRecentCall.object = this;
383     spyObj.mostRecentCall.args = args;
384     spyObj.argsForCall.push(args);
385     spyObj.calls.push({object: this, args: args});
386     return spyObj.plan.apply(this, arguments);
387   };
388
389   var spy = new jasmine.Spy(name);
390
391   for (var prop in spy) {
392     spyObj[prop] = spy[prop];
393   }
394
395   spyObj.reset();
396
397   return spyObj;
398 };
399
400 /**
401  * Determines whether an object is a spy.
402  *
403  * @param {jasmine.Spy|Object} putativeSpy
404  * @returns {Boolean}
405  */
406 jasmine.isSpy = function(putativeSpy) {
407   return putativeSpy && putativeSpy.isSpy;
408 };
409
410 /**
411  * Creates a more complicated spy: an Object that has every property a function that is a spy.  Used for stubbing something
412  * large in one call.
413  *
414  * @param {String} baseName name of spy class
415  * @param {Array} methodNames array of names of methods to make spies
416  */
417 jasmine.createSpyObj = function(baseName, methodNames) {
418   if (!jasmine.isArray_(methodNames) || methodNames.length === 0) {
419     throw new Error('createSpyObj requires a non-empty array of method names to create spies for');
420   }
421   var obj = {};
422   for (var i = 0; i < methodNames.length; i++) {
423     obj[methodNames[i]] = jasmine.createSpy(baseName + '.' + methodNames[i]);
424   }
425   return obj;
426 };
427
428 /**
429  * All parameters are pretty-printed and concatenated together, then written to the current spec's output.
430  *
431  * Be careful not to leave calls to <code>jasmine.log</code> in production code.
432  */
433 jasmine.log = function() {
434   var spec = jasmine.getEnv().currentSpec;
435   spec.log.apply(spec, arguments);
436 };
437
438 /**
439  * Function that installs a spy on an existing object's method name.  Used within a Spec to create a spy.
440  *
441  * @example
442  * // spy example
443  * var foo = {
444  *   not: function(bool) { return !bool; }
445  * }
446  * spyOn(foo, 'not'); // actual foo.not will not be called, execution stops
447  *
448  * @see jasmine.createSpy
449  * @param obj
450  * @param methodName
451  * @returns a Jasmine spy that can be chained with all spy methods
452  */
453 var spyOn = function(obj, methodName) {
454   return jasmine.getEnv().currentSpec.spyOn(obj, methodName);
455 };
456 if (isCommonJS) exports.spyOn = spyOn;
457
458 /**
459  * Creates a Jasmine spec that will be added to the current suite.
460  *
461  * // TODO: pending tests
462  *
463  * @example
464  * it('should be true', function() {
465  *   expect(true).toEqual(true);
466  * });
467  *
468  * @param {String} desc description of this specification
469  * @param {Function} func defines the preconditions and expectations of the spec
470  */
471 var it = function(desc, func) {
472   return jasmine.getEnv().it(desc, func);
473 };
474 if (isCommonJS) exports.it = it;
475
476 /**
477  * Creates a <em>disabled</em> Jasmine spec.
478  *
479  * A convenience method that allows existing specs to be disabled temporarily during development.
480  *
481  * @param {String} desc description of this specification
482  * @param {Function} func defines the preconditions and expectations of the spec
483  */
484 var xit = function(desc, func) {
485   return jasmine.getEnv().xit(desc, func);
486 };
487 if (isCommonJS) exports.xit = xit;
488
489 /**
490  * Starts a chain for a Jasmine expectation.
491  *
492  * It is passed an Object that is the actual value and should chain to one of the many
493  * jasmine.Matchers functions.
494  *
495  * @param {Object} actual Actual value to test against and expected value
496  */
497 var expect = function(actual) {
498   return jasmine.getEnv().currentSpec.expect(actual);
499 };
500 if (isCommonJS) exports.expect = expect;
501
502 /**
503  * Defines part of a jasmine spec.  Used in cominbination with waits or waitsFor in asynchrnous specs.
504  *
505  * @param {Function} func Function that defines part of a jasmine spec.
506  */
507 var runs = function(func) {
508   jasmine.getEnv().currentSpec.runs(func);
509 };
510 if (isCommonJS) exports.runs = runs;
511
512 /**
513  * Waits a fixed time period before moving to the next block.
514  *
515  * @deprecated Use waitsFor() instead
516  * @param {Number} timeout milliseconds to wait
517  */
518 var waits = function(timeout) {
519   jasmine.getEnv().currentSpec.waits(timeout);
520 };
521 if (isCommonJS) exports.waits = waits;
522
523 /**
524  * Waits for the latchFunction to return true before proceeding to the next block.
525  *
526  * @param {Function} latchFunction
527  * @param {String} optional_timeoutMessage
528  * @param {Number} optional_timeout
529  */
530 var waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) {
531   jasmine.getEnv().currentSpec.waitsFor.apply(jasmine.getEnv().currentSpec, arguments);
532 };
533 if (isCommonJS) exports.waitsFor = waitsFor;
534
535 /**
536  * A function that is called before each spec in a suite.
537  *
538  * Used for spec setup, including validating assumptions.
539  *
540  * @param {Function} beforeEachFunction
541  */
542 var beforeEach = function(beforeEachFunction) {
543   jasmine.getEnv().beforeEach(beforeEachFunction);
544 };
545 if (isCommonJS) exports.beforeEach = beforeEach;
546
547 /**
548  * A function that is called after each spec in a suite.
549  *
550  * Used for restoring any state that is hijacked during spec execution.
551  *
552  * @param {Function} afterEachFunction
553  */
554 var afterEach = function(afterEachFunction) {
555   jasmine.getEnv().afterEach(afterEachFunction);
556 };
557 if (isCommonJS) exports.afterEach = afterEach;
558
559 /**
560  * Defines a suite of specifications.
561  *
562  * Stores the description and all defined specs in the Jasmine environment as one suite of specs. Variables declared
563  * are accessible by calls to beforeEach, it, and afterEach. Describe blocks can be nested, allowing for specialization
564  * of setup in some tests.
565  *
566  * @example
567  * // TODO: a simple suite
568  *
569  * // TODO: a simple suite with a nested describe block
570  *
571  * @param {String} description A string, usually the class under test.
572  * @param {Function} specDefinitions function that defines several specs.
573  */
574 var describe = function(description, specDefinitions) {
575   return jasmine.getEnv().describe(description, specDefinitions);
576 };
577 if (isCommonJS) exports.describe = describe;
578
579 /**
580  * Disables a suite of specifications.  Used to disable some suites in a file, or files, temporarily during development.
581  *
582  * @param {String} description A string, usually the class under test.
583  * @param {Function} specDefinitions function that defines several specs.
584  */
585 var xdescribe = function(description, specDefinitions) {
586   return jasmine.getEnv().xdescribe(description, specDefinitions);
587 };
588 if (isCommonJS) exports.xdescribe = xdescribe;
589
590
591 // Provide the XMLHttpRequest class for IE 5.x-6.x:
592 jasmine.XmlHttpRequest = (typeof XMLHttpRequest == "undefined") ? function() {
593   function tryIt(f) {
594     try {
595       return f();
596     } catch(e) {
597     }
598     return null;
599   }
600
601   var xhr = tryIt(function() {
602     return new ActiveXObject("Msxml2.XMLHTTP.6.0");
603   }) ||
604     tryIt(function() {
605       return new ActiveXObject("Msxml2.XMLHTTP.3.0");
606     }) ||
607     tryIt(function() {
608       return new ActiveXObject("Msxml2.XMLHTTP");
609     }) ||
610     tryIt(function() {
611       return new ActiveXObject("Microsoft.XMLHTTP");
612     });
613
614   if (!xhr) throw new Error("This browser does not support XMLHttpRequest.");
615
616   return xhr;
617 } : XMLHttpRequest;
618 /**
619  * @namespace
620  */
621 jasmine.util = {};
622
623 /**
624  * Declare that a child class inherit it's prototype from the parent class.
625  *
626  * @private
627  * @param {Function} childClass
628  * @param {Function} parentClass
629  */
630 jasmine.util.inherit = function(childClass, parentClass) {
631   /**
632    * @private
633    */
634   var subclass = function() {
635   };
636   subclass.prototype = parentClass.prototype;
637   childClass.prototype = new subclass();
638 };
639
640 jasmine.util.formatException = function(e) {
641   var lineNumber;
642   if (e.line) {
643     lineNumber = e.line;
644   }
645   else if (e.lineNumber) {
646     lineNumber = e.lineNumber;
647   }
648
649   var file;
650
651   if (e.sourceURL) {
652     file = e.sourceURL;
653   }
654   else if (e.fileName) {
655     file = e.fileName;
656   }
657
658   var message = (e.name && e.message) ? (e.name + ': ' + e.message) : e.toString();
659
660   if (file && lineNumber) {
661     message += ' in ' + file + ' (line ' + lineNumber + ')';
662   }
663
664   return message;
665 };
666
667 jasmine.util.htmlEscape = function(str) {
668   if (!str) return str;
669   return str.replace(/&/g, '&amp;')
670     .replace(/</g, '&lt;')
671     .replace(/>/g, '&gt;');
672 };
673
674 jasmine.util.argsToArray = function(args) {
675   var arrayOfArgs = [];
676   for (var i = 0; i < args.length; i++) arrayOfArgs.push(args[i]);
677   return arrayOfArgs;
678 };
679
680 jasmine.util.extend = function(destination, source) {
681   for (var property in source) destination[property] = source[property];
682   return destination;
683 };
684
685 /**
686  * Environment for Jasmine
687  *
688  * @constructor
689  */
690 jasmine.Env = function() {
691   this.currentSpec = null;
692   this.currentSuite = null;
693   this.currentRunner_ = new jasmine.Runner(this);
694
695   this.reporter = new jasmine.MultiReporter();
696
697   this.updateInterval = jasmine.DEFAULT_UPDATE_INTERVAL;
698   this.defaultTimeoutInterval = jasmine.DEFAULT_TIMEOUT_INTERVAL;
699   this.lastUpdate = 0;
700   this.specFilter = function() {
701     return true;
702   };
703
704   this.nextSpecId_ = 0;
705   this.nextSuiteId_ = 0;
706   this.equalityTesters_ = [];
707
708   // wrap matchers
709   this.matchersClass = function() {
710     jasmine.Matchers.apply(this, arguments);
711   };
712   jasmine.util.inherit(this.matchersClass, jasmine.Matchers);
713
714   jasmine.Matchers.wrapInto_(jasmine.Matchers.prototype, this.matchersClass);
715 };
716
717
718 jasmine.Env.prototype.setTimeout = jasmine.setTimeout;
719 jasmine.Env.prototype.clearTimeout = jasmine.clearTimeout;
720 jasmine.Env.prototype.setInterval = jasmine.setInterval;
721 jasmine.Env.prototype.clearInterval = jasmine.clearInterval;
722
723 /**
724  * @returns an object containing jasmine version build info, if set.
725  */
726 jasmine.Env.prototype.version = function () {
727   if (jasmine.version_) {
728     return jasmine.version_;
729   } else {
730     throw new Error('Version not set');
731   }
732 };
733
734 /**
735  * @returns string containing jasmine version build info, if set.
736  */
737 jasmine.Env.prototype.versionString = function() {
738   if (!jasmine.version_) {
739     return "version unknown";
740   }
741
742   var version = this.version();
743   var dotted_version = version.major + "." + version.minor + "." + version.build;
744   if (version.rc) {
745     dotted_version += ".rc" + version.rc;
746   }
747   return dotted_version + " revision " + version.revision;
748 };
749
750 /**
751  * @returns a sequential integer starting at 0
752  */
753 jasmine.Env.prototype.nextSpecId = function () {
754   return this.nextSpecId_++;
755 };
756
757 /**
758  * @returns a sequential integer starting at 0
759  */
760 jasmine.Env.prototype.nextSuiteId = function () {
761   return this.nextSuiteId_++;
762 };
763
764 /**
765  * Register a reporter to receive status updates from Jasmine.
766  * @param {jasmine.Reporter} reporter An object which will receive status updates.
767  */
768 jasmine.Env.prototype.addReporter = function(reporter) {
769   this.reporter.addReporter(reporter);
770 };
771
772 jasmine.Env.prototype.execute = function() {
773   this.currentRunner_.execute();
774 };
775
776 jasmine.Env.prototype.describe = function(description, specDefinitions) {
777   var suite = new jasmine.Suite(this, description, specDefinitions, this.currentSuite);
778
779   var parentSuite = this.currentSuite;
780   if (parentSuite) {
781     parentSuite.add(suite);
782   } else {
783     this.currentRunner_.add(suite);
784   }
785
786   this.currentSuite = suite;
787
788   var declarationError = null;
789   try {
790     specDefinitions.call(suite);
791   } catch(e) {
792     declarationError = e;
793   }
794
795   if (declarationError) {
796     this.it("encountered a declaration exception", function() {
797       throw declarationError;
798     });
799   }
800
801   this.currentSuite = parentSuite;
802
803   return suite;
804 };
805
806 jasmine.Env.prototype.beforeEach = function(beforeEachFunction) {
807   if (this.currentSuite) {
808     this.currentSuite.beforeEach(beforeEachFunction);
809   } else {
810     this.currentRunner_.beforeEach(beforeEachFunction);
811   }
812 };
813
814 jasmine.Env.prototype.currentRunner = function () {
815   return this.currentRunner_;
816 };
817
818 jasmine.Env.prototype.afterEach = function(afterEachFunction) {
819   if (this.currentSuite) {
820     this.currentSuite.afterEach(afterEachFunction);
821   } else {
822     this.currentRunner_.afterEach(afterEachFunction);
823   }
824
825 };
826
827 jasmine.Env.prototype.xdescribe = function(desc, specDefinitions) {
828   return {
829     execute: function() {
830     }
831   };
832 };
833
834 jasmine.Env.prototype.it = function(description, func) {
835   var spec = new jasmine.Spec(this, this.currentSuite, description);
836   this.currentSuite.add(spec);
837   this.currentSpec = spec;
838
839   if (func) {
840     spec.runs(func);
841   }
842
843   return spec;
844 };
845
846 jasmine.Env.prototype.xit = function(desc, func) {
847   return {
848     id: this.nextSpecId(),
849     runs: function() {
850     }
851   };
852 };
853
854 jasmine.Env.prototype.compareObjects_ = function(a, b, mismatchKeys, mismatchValues) {
855   if (a.__Jasmine_been_here_before__ === b && b.__Jasmine_been_here_before__ === a) {
856     return true;
857   }
858
859   a.__Jasmine_been_here_before__ = b;
860   b.__Jasmine_been_here_before__ = a;
861
862   var hasKey = function(obj, keyName) {
863     return obj !== null && obj[keyName] !== jasmine.undefined;
864   };
865
866   for (var property in b) {
867     if (!hasKey(a, property) && hasKey(b, property)) {
868       mismatchKeys.push("expected has key '" + property + "', but missing from actual.");
869     }
870   }
871   for (property in a) {
872     if (!hasKey(b, property) && hasKey(a, property)) {
873       mismatchKeys.push("expected missing key '" + property + "', but present in actual.");
874     }
875   }
876   for (property in b) {
877     if (property == '__Jasmine_been_here_before__') continue;
878     if (!this.equals_(a[property], b[property], mismatchKeys, mismatchValues)) {
879       mismatchValues.push("'" + property + "' was '" + (b[property] ? jasmine.util.htmlEscape(b[property].toString()) : b[property]) + "' in expected, but was '" + (a[property] ? jasmine.util.htmlEscape(a[property].toString()) : a[property]) + "' in actual.");
880     }
881   }
882
883   if (jasmine.isArray_(a) && jasmine.isArray_(b) && a.length != b.length) {
884     mismatchValues.push("arrays were not the same length");
885   }
886
887   delete a.__Jasmine_been_here_before__;
888   delete b.__Jasmine_been_here_before__;
889   return (mismatchKeys.length === 0 && mismatchValues.length === 0);
890 };
891
892 jasmine.Env.prototype.equals_ = function(a, b, mismatchKeys, mismatchValues) {
893   mismatchKeys = mismatchKeys || [];
894   mismatchValues = mismatchValues || [];
895
896   for (var i = 0; i < this.equalityTesters_.length; i++) {
897     var equalityTester = this.equalityTesters_[i];
898     var result = equalityTester(a, b, this, mismatchKeys, mismatchValues);
899     if (result !== jasmine.undefined) return result;
900   }
901
902   if (a === b) return true;
903
904   if (a === jasmine.undefined || a === null || b === jasmine.undefined || b === null) {
905     return (a == jasmine.undefined && b == jasmine.undefined);
906   }
907
908   if (jasmine.isDomNode(a) && jasmine.isDomNode(b)) {
909     return a === b;
910   }
911
912   if (a instanceof Date && b instanceof Date) {
913     return a.getTime() == b.getTime();
914   }
915
916   if (a instanceof jasmine.Matchers.Any) {
917     return a.matches(b);
918   }
919
920   if (b instanceof jasmine.Matchers.Any) {
921     return b.matches(a);
922   }
923
924   if (jasmine.isString_(a) && jasmine.isString_(b)) {
925     return (a == b);
926   }
927
928   if (jasmine.isNumber_(a) && jasmine.isNumber_(b)) {
929     return (a == b);
930   }
931
932   if (typeof a === "object" && typeof b === "object") {
933     return this.compareObjects_(a, b, mismatchKeys, mismatchValues);
934   }
935
936   //Straight check
937   return (a === b);
938 };
939
940 jasmine.Env.prototype.contains_ = function(haystack, needle) {
941   if (jasmine.isArray_(haystack)) {
942     for (var i = 0; i < haystack.length; i++) {
943       if (this.equals_(haystack[i], needle)) return true;
944     }
945     return false;
946   }
947   return haystack.indexOf(needle) >= 0;
948 };
949
950 jasmine.Env.prototype.addEqualityTester = function(equalityTester) {
951   this.equalityTesters_.push(equalityTester);
952 };
953 /** No-op base class for Jasmine reporters.
954  *
955  * @constructor
956  */
957 jasmine.Reporter = function() {
958 };
959
960 //noinspection JSUnusedLocalSymbols
961 jasmine.Reporter.prototype.reportRunnerStarting = function(runner) {
962 };
963
964 //noinspection JSUnusedLocalSymbols
965 jasmine.Reporter.prototype.reportRunnerResults = function(runner) {
966 };
967
968 //noinspection JSUnusedLocalSymbols
969 jasmine.Reporter.prototype.reportSuiteResults = function(suite) {
970 };
971
972 //noinspection JSUnusedLocalSymbols
973 jasmine.Reporter.prototype.reportSpecStarting = function(spec) {
974 };
975
976 //noinspection JSUnusedLocalSymbols
977 jasmine.Reporter.prototype.reportSpecResults = function(spec) {
978 };
979
980 //noinspection JSUnusedLocalSymbols
981 jasmine.Reporter.prototype.log = function(str) {
982 };
983
984 /**
985  * Blocks are functions with executable code that make up a spec.
986  *
987  * @constructor
988  * @param {jasmine.Env} env
989  * @param {Function} func
990  * @param {jasmine.Spec} spec
991  */
992 jasmine.Block = function(env, func, spec) {
993   this.env = env;
994   this.func = func;
995   this.spec = spec;
996 };
997
998 jasmine.Block.prototype.execute = function(onComplete) {  
999   try {
1000     this.func.apply(this.spec);
1001   } catch (e) {
1002     this.spec.fail(e);
1003   }
1004   onComplete();
1005 };
1006 /** JavaScript API reporter.
1007  *
1008  * @constructor
1009  */
1010 jasmine.JsApiReporter = function() {
1011   this.started = false;
1012   this.finished = false;
1013   this.suites_ = [];
1014   this.results_ = {};
1015 };
1016
1017 jasmine.JsApiReporter.prototype.reportRunnerStarting = function(runner) {
1018   this.started = true;
1019   var suites = runner.topLevelSuites();
1020   for (var i = 0; i < suites.length; i++) {
1021     var suite = suites[i];
1022     this.suites_.push(this.summarize_(suite));
1023   }
1024 };
1025
1026 jasmine.JsApiReporter.prototype.suites = function() {
1027   return this.suites_;
1028 };
1029
1030 jasmine.JsApiReporter.prototype.summarize_ = function(suiteOrSpec) {
1031   var isSuite = suiteOrSpec instanceof jasmine.Suite;
1032   var summary = {
1033     id: suiteOrSpec.id,
1034     name: suiteOrSpec.description,
1035     type: isSuite ? 'suite' : 'spec',
1036     children: []
1037   };
1038   
1039   if (isSuite) {
1040     var children = suiteOrSpec.children();
1041     for (var i = 0; i < children.length; i++) {
1042       summary.children.push(this.summarize_(children[i]));
1043     }
1044   }
1045   return summary;
1046 };
1047
1048 jasmine.JsApiReporter.prototype.results = function() {
1049   return this.results_;
1050 };
1051
1052 jasmine.JsApiReporter.prototype.resultsForSpec = function(specId) {
1053   return this.results_[specId];
1054 };
1055
1056 //noinspection JSUnusedLocalSymbols
1057 jasmine.JsApiReporter.prototype.reportRunnerResults = function(runner) {
1058   this.finished = true;
1059 };
1060
1061 //noinspection JSUnusedLocalSymbols
1062 jasmine.JsApiReporter.prototype.reportSuiteResults = function(suite) {
1063 };
1064
1065 //noinspection JSUnusedLocalSymbols
1066 jasmine.JsApiReporter.prototype.reportSpecResults = function(spec) {
1067   this.results_[spec.id] = {
1068     messages: spec.results().getItems(),
1069     result: spec.results().failedCount > 0 ? "failed" : "passed"
1070   };
1071 };
1072
1073 //noinspection JSUnusedLocalSymbols
1074 jasmine.JsApiReporter.prototype.log = function(str) {
1075 };
1076
1077 jasmine.JsApiReporter.prototype.resultsForSpecs = function(specIds){
1078   var results = {};
1079   for (var i = 0; i < specIds.length; i++) {
1080     var specId = specIds[i];
1081     results[specId] = this.summarizeResult_(this.results_[specId]);
1082   }
1083   return results;
1084 };
1085
1086 jasmine.JsApiReporter.prototype.summarizeResult_ = function(result){
1087   var summaryMessages = [];
1088   var messagesLength = result.messages.length;
1089   for (var messageIndex = 0; messageIndex < messagesLength; messageIndex++) {
1090     var resultMessage = result.messages[messageIndex];
1091     summaryMessages.push({
1092       text: resultMessage.type == 'log' ? resultMessage.toString() : jasmine.undefined,
1093       passed: resultMessage.passed ? resultMessage.passed() : true,
1094       type: resultMessage.type,
1095       message: resultMessage.message,
1096       trace: {
1097         stack: resultMessage.passed && !resultMessage.passed() ? resultMessage.trace.stack : jasmine.undefined
1098       }
1099     });
1100   }
1101
1102   return {
1103     result : result.result,
1104     messages : summaryMessages
1105   };
1106 };
1107
1108 /**
1109  * @constructor
1110  * @param {jasmine.Env} env
1111  * @param actual
1112  * @param {jasmine.Spec} spec
1113  */
1114 jasmine.Matchers = function(env, actual, spec, opt_isNot) {
1115   this.env = env;
1116   this.actual = actual;
1117   this.spec = spec;
1118   this.isNot = opt_isNot || false;
1119   this.reportWasCalled_ = false;
1120 };
1121
1122 // todo: @deprecated as of Jasmine 0.11, remove soon [xw]
1123 jasmine.Matchers.pp = function(str) {
1124   throw new Error("jasmine.Matchers.pp() is no longer supported, please use jasmine.pp() instead!");
1125 };
1126
1127 // todo: @deprecated Deprecated as of Jasmine 0.10. Rewrite your custom matchers to return true or false. [xw]
1128 jasmine.Matchers.prototype.report = function(result, failing_message, details) {
1129   throw new Error("As of jasmine 0.11, custom matchers must be implemented differently -- please see jasmine docs");
1130 };
1131
1132 jasmine.Matchers.wrapInto_ = function(prototype, matchersClass) {
1133   for (var methodName in prototype) {
1134     if (methodName == 'report') continue;
1135     var orig = prototype[methodName];
1136     matchersClass.prototype[methodName] = jasmine.Matchers.matcherFn_(methodName, orig);
1137   }
1138 };
1139
1140 jasmine.Matchers.matcherFn_ = function(matcherName, matcherFunction) {
1141   return function() {
1142     var matcherArgs = jasmine.util.argsToArray(arguments);
1143     var result = matcherFunction.apply(this, arguments);
1144
1145     if (this.isNot) {
1146       result = !result;
1147     }
1148
1149     if (this.reportWasCalled_) return result;
1150
1151     var message;
1152     if (!result) {
1153       if (this.message) {
1154         message = this.message.apply(this, arguments);
1155         if (jasmine.isArray_(message)) {
1156           message = message[this.isNot ? 1 : 0];
1157         }
1158       } else {
1159         var englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); });
1160         message = "Expected " + jasmine.pp(this.actual) + (this.isNot ? " not " : " ") + englishyPredicate;
1161         if (matcherArgs.length > 0) {
1162           for (var i = 0; i < matcherArgs.length; i++) {
1163             if (i > 0) message += ",";
1164             message += " " + jasmine.pp(matcherArgs[i]);
1165           }
1166         }
1167         message += ".";
1168       }
1169     }
1170     var expectationResult = new jasmine.ExpectationResult({
1171       matcherName: matcherName,
1172       passed: result,
1173       expected: matcherArgs.length > 1 ? matcherArgs : matcherArgs[0],
1174       actual: this.actual,
1175       message: message
1176     });
1177     this.spec.addMatcherResult(expectationResult);
1178     return jasmine.undefined;
1179   };
1180 };
1181
1182
1183
1184
1185 /**
1186  * toBe: compares the actual to the expected using ===
1187  * @param expected
1188  */
1189 jasmine.Matchers.prototype.toBe = function(expected) {
1190   return this.actual === expected;
1191 };
1192
1193 /**
1194  * toNotBe: compares the actual to the expected using !==
1195  * @param expected
1196  * @deprecated as of 1.0. Use not.toBe() instead.
1197  */
1198 jasmine.Matchers.prototype.toNotBe = function(expected) {
1199   return this.actual !== expected;
1200 };
1201
1202 /**
1203  * toEqual: compares the actual to the expected using common sense equality. Handles Objects, Arrays, etc.
1204  *
1205  * @param expected
1206  */
1207 jasmine.Matchers.prototype.toEqual = function(expected) {
1208   return this.env.equals_(this.actual, expected);
1209 };
1210
1211 /**
1212  * toNotEqual: compares the actual to the expected using the ! of jasmine.Matchers.toEqual
1213  * @param expected
1214  * @deprecated as of 1.0. Use not.toNotEqual() instead.
1215  */
1216 jasmine.Matchers.prototype.toNotEqual = function(expected) {
1217   return !this.env.equals_(this.actual, expected);
1218 };
1219
1220 /**
1221  * Matcher that compares the actual to the expected using a regular expression.  Constructs a RegExp, so takes
1222  * a pattern or a String.
1223  *
1224  * @param expected
1225  */
1226 jasmine.Matchers.prototype.toMatch = function(expected) {
1227   return new RegExp(expected).test(this.actual);
1228 };
1229
1230 /**
1231  * Matcher that compares the actual to the expected using the boolean inverse of jasmine.Matchers.toMatch
1232  * @param expected
1233  * @deprecated as of 1.0. Use not.toMatch() instead.
1234  */
1235 jasmine.Matchers.prototype.toNotMatch = function(expected) {
1236   return !(new RegExp(expected).test(this.actual));
1237 };
1238
1239 /**
1240  * Matcher that compares the actual to jasmine.undefined.
1241  */
1242 jasmine.Matchers.prototype.toBeDefined = function() {
1243   return (this.actual !== jasmine.undefined);
1244 };
1245
1246 /**
1247  * Matcher that compares the actual to jasmine.undefined.
1248  */
1249 jasmine.Matchers.prototype.toBeUndefined = function() {
1250   return (this.actual === jasmine.undefined);
1251 };
1252
1253 /**
1254  * Matcher that compares the actual to null.
1255  */
1256 jasmine.Matchers.prototype.toBeNull = function() {
1257   return (this.actual === null);
1258 };
1259
1260 /**
1261  * Matcher that boolean not-nots the actual.
1262  */
1263 jasmine.Matchers.prototype.toBeTruthy = function() {
1264   return !!this.actual;
1265 };
1266
1267
1268 /**
1269  * Matcher that boolean nots the actual.
1270  */
1271 jasmine.Matchers.prototype.toBeFalsy = function() {
1272   return !this.actual;
1273 };
1274
1275
1276 /**
1277  * Matcher that checks to see if the actual, a Jasmine spy, was called.
1278  */
1279 jasmine.Matchers.prototype.toHaveBeenCalled = function() {
1280   if (arguments.length > 0) {
1281     throw new Error('toHaveBeenCalled does not take arguments, use toHaveBeenCalledWith');
1282   }
1283
1284   if (!jasmine.isSpy(this.actual)) {
1285     throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
1286   }
1287
1288   this.message = function() {
1289     return [
1290       "Expected spy " + this.actual.identity + " to have been called.",
1291       "Expected spy " + this.actual.identity + " not to have been called."
1292     ];
1293   };
1294
1295   return this.actual.wasCalled;
1296 };
1297
1298 /** @deprecated Use expect(xxx).toHaveBeenCalled() instead */
1299 jasmine.Matchers.prototype.wasCalled = jasmine.Matchers.prototype.toHaveBeenCalled;
1300
1301 /**
1302  * Matcher that checks to see if the actual, a Jasmine spy, was not called.
1303  *
1304  * @deprecated Use expect(xxx).not.toHaveBeenCalled() instead
1305  */
1306 jasmine.Matchers.prototype.wasNotCalled = function() {
1307   if (arguments.length > 0) {
1308     throw new Error('wasNotCalled does not take arguments');
1309   }
1310
1311   if (!jasmine.isSpy(this.actual)) {
1312     throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
1313   }
1314
1315   this.message = function() {
1316     return [
1317       "Expected spy " + this.actual.identity + " to not have been called.",
1318       "Expected spy " + this.actual.identity + " to have been called."
1319     ];
1320   };
1321
1322   return !this.actual.wasCalled;
1323 };
1324
1325 /**
1326  * Matcher that checks to see if the actual, a Jasmine spy, was called with a set of parameters.
1327  *
1328  * @example
1329  *
1330  */
1331 jasmine.Matchers.prototype.toHaveBeenCalledWith = function() {
1332   var expectedArgs = jasmine.util.argsToArray(arguments);
1333   if (!jasmine.isSpy(this.actual)) {
1334     throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
1335   }
1336   this.message = function() {
1337     if (this.actual.callCount === 0) {
1338       // todo: what should the failure message for .not.toHaveBeenCalledWith() be? is this right? test better. [xw]
1339       return [
1340         "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but it was never called.",
1341         "Expected spy " + this.actual.identity + " not to have been called with " + jasmine.pp(expectedArgs) + " but it was."
1342       ];
1343     } else {
1344       return [
1345         "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall),
1346         "Expected spy " + this.actual.identity + " not to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall)
1347       ];
1348     }
1349   };
1350
1351   return this.env.contains_(this.actual.argsForCall, expectedArgs);
1352 };
1353
1354 /** @deprecated Use expect(xxx).toHaveBeenCalledWith() instead */
1355 jasmine.Matchers.prototype.wasCalledWith = jasmine.Matchers.prototype.toHaveBeenCalledWith;
1356
1357 /** @deprecated Use expect(xxx).not.toHaveBeenCalledWith() instead */
1358 jasmine.Matchers.prototype.wasNotCalledWith = function() {
1359   var expectedArgs = jasmine.util.argsToArray(arguments);
1360   if (!jasmine.isSpy(this.actual)) {
1361     throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
1362   }
1363
1364   this.message = function() {
1365     return [
1366       "Expected spy not to have been called with " + jasmine.pp(expectedArgs) + " but it was",
1367       "Expected spy to have been called with " + jasmine.pp(expectedArgs) + " but it was"
1368     ];
1369   };
1370
1371   return !this.env.contains_(this.actual.argsForCall, expectedArgs);
1372 };
1373
1374 /**
1375  * Matcher that checks that the expected item is an element in the actual Array.
1376  *
1377  * @param {Object} expected
1378  */
1379 jasmine.Matchers.prototype.toContain = function(expected) {
1380   return this.env.contains_(this.actual, expected);
1381 };
1382
1383 /**
1384  * Matcher that checks that the expected item is NOT an element in the actual Array.
1385  *
1386  * @param {Object} expected
1387  * @deprecated as of 1.0. Use not.toNotContain() instead.
1388  */
1389 jasmine.Matchers.prototype.toNotContain = function(expected) {
1390   return !this.env.contains_(this.actual, expected);
1391 };
1392
1393 jasmine.Matchers.prototype.toBeLessThan = function(expected) {
1394   return this.actual < expected;
1395 };
1396
1397 jasmine.Matchers.prototype.toBeGreaterThan = function(expected) {
1398   return this.actual > expected;
1399 };
1400
1401 /**
1402  * Matcher that checks that the expected item is equal to the actual item
1403  * up to a given level of decimal precision (default 2).
1404  *
1405  * @param {Number} expected
1406  * @param {Number} precision
1407  */
1408 jasmine.Matchers.prototype.toBeCloseTo = function(expected, precision) {
1409   if (!(precision === 0)) {
1410     precision = precision || 2;
1411   }
1412   var multiplier = Math.pow(10, precision);
1413   var actual = Math.round(this.actual * multiplier);
1414   expected = Math.round(expected * multiplier);
1415   return expected == actual;
1416 };
1417
1418 /**
1419  * Matcher that checks that the expected exception was thrown by the actual.
1420  *
1421  * @param {String} expected
1422  */
1423 jasmine.Matchers.prototype.toThrow = function(expected) {
1424   var result = false;
1425   var exception;
1426   if (typeof this.actual != 'function') {
1427     throw new Error('Actual is not a function');
1428   }
1429   try {
1430     this.actual();
1431   } catch (e) {
1432     exception = e;
1433   }
1434   if (exception) {
1435     result = (expected === jasmine.undefined || this.env.equals_(exception.message || exception, expected.message || expected));
1436   }
1437
1438   var not = this.isNot ? "not " : "";
1439
1440   this.message = function() {
1441     if (exception && (expected === jasmine.undefined || !this.env.equals_(exception.message || exception, expected.message || expected))) {
1442       return ["Expected function " + not + "to throw", expected ? expected.message || expected : "an exception", ", but it threw", exception.message || exception].join(' ');
1443     } else {
1444       return "Expected function to throw an exception.";
1445     }
1446   };
1447
1448   return result;
1449 };
1450
1451 jasmine.Matchers.Any = function(expectedClass) {
1452   this.expectedClass = expectedClass;
1453 };
1454
1455 jasmine.Matchers.Any.prototype.matches = function(other) {
1456   if (this.expectedClass == String) {
1457     return typeof other == 'string' || other instanceof String;
1458   }
1459
1460   if (this.expectedClass == Number) {
1461     return typeof other == 'number' || other instanceof Number;
1462   }
1463
1464   if (this.expectedClass == Function) {
1465     return typeof other == 'function' || other instanceof Function;
1466   }
1467
1468   if (this.expectedClass == Object) {
1469     return typeof other == 'object';
1470   }
1471
1472   return other instanceof this.expectedClass;
1473 };
1474
1475 jasmine.Matchers.Any.prototype.toString = function() {
1476   return '<jasmine.any(' + this.expectedClass + ')>';
1477 };
1478
1479 /**
1480  * @constructor
1481  */
1482 jasmine.MultiReporter = function() {
1483   this.subReporters_ = [];
1484 };
1485 jasmine.util.inherit(jasmine.MultiReporter, jasmine.Reporter);
1486
1487 jasmine.MultiReporter.prototype.addReporter = function(reporter) {
1488   this.subReporters_.push(reporter);
1489 };
1490
1491 (function() {
1492   var functionNames = [
1493     "reportRunnerStarting",
1494     "reportRunnerResults",
1495     "reportSuiteResults",
1496     "reportSpecStarting",
1497     "reportSpecResults",
1498     "log"
1499   ];
1500   for (var i = 0; i < functionNames.length; i++) {
1501     var functionName = functionNames[i];
1502     jasmine.MultiReporter.prototype[functionName] = (function(functionName) {
1503       return function() {
1504         for (var j = 0; j < this.subReporters_.length; j++) {
1505           var subReporter = this.subReporters_[j];
1506           if (subReporter[functionName]) {
1507             subReporter[functionName].apply(subReporter, arguments);
1508           }
1509         }
1510       };
1511     })(functionName);
1512   }
1513 })();
1514 /**
1515  * Holds results for a set of Jasmine spec. Allows for the results array to hold another jasmine.NestedResults
1516  *
1517  * @constructor
1518  */
1519 jasmine.NestedResults = function() {
1520   /**
1521    * The total count of results
1522    */
1523   this.totalCount = 0;
1524   /**
1525    * Number of passed results
1526    */
1527   this.passedCount = 0;
1528   /**
1529    * Number of failed results
1530    */
1531   this.failedCount = 0;
1532   /**
1533    * Was this suite/spec skipped?
1534    */
1535   this.skipped = false;
1536   /**
1537    * @ignore
1538    */
1539   this.items_ = [];
1540 };
1541
1542 /**
1543  * Roll up the result counts.
1544  *
1545  * @param result
1546  */
1547 jasmine.NestedResults.prototype.rollupCounts = function(result) {
1548   this.totalCount += result.totalCount;
1549   this.passedCount += result.passedCount;
1550   this.failedCount += result.failedCount;
1551 };
1552
1553 /**
1554  * Adds a log message.
1555  * @param values Array of message parts which will be concatenated later.
1556  */
1557 jasmine.NestedResults.prototype.log = function(values) {
1558   this.items_.push(new jasmine.MessageResult(values));
1559 };
1560
1561 /**
1562  * Getter for the results: message & results.
1563  */
1564 jasmine.NestedResults.prototype.getItems = function() {
1565   return this.items_;
1566 };
1567
1568 /**
1569  * Adds a result, tracking counts (total, passed, & failed)
1570  * @param {jasmine.ExpectationResult|jasmine.NestedResults} result
1571  */
1572 jasmine.NestedResults.prototype.addResult = function(result) {
1573   if (result.type != 'log') {
1574     if (result.items_) {
1575       this.rollupCounts(result);
1576     } else {
1577       this.totalCount++;
1578       if (result.passed()) {
1579         this.passedCount++;
1580       } else {
1581         this.failedCount++;
1582       }
1583     }
1584   }
1585   this.items_.push(result);
1586 };
1587
1588 /**
1589  * @returns {Boolean} True if <b>everything</b> below passed
1590  */
1591 jasmine.NestedResults.prototype.passed = function() {
1592   return this.passedCount === this.totalCount;
1593 };
1594 /**
1595  * Base class for pretty printing for expectation results.
1596  */
1597 jasmine.PrettyPrinter = function() {
1598   this.ppNestLevel_ = 0;
1599 };
1600
1601 /**
1602  * Formats a value in a nice, human-readable string.
1603  *
1604  * @param value
1605  */
1606 jasmine.PrettyPrinter.prototype.format = function(value) {
1607   if (this.ppNestLevel_ > 40) {
1608     throw new Error('jasmine.PrettyPrinter: format() nested too deeply!');
1609   }
1610
1611   this.ppNestLevel_++;
1612   try {
1613     if (value === jasmine.undefined) {
1614       this.emitScalar('undefined');
1615     } else if (value === null) {
1616       this.emitScalar('null');
1617     } else if (value === jasmine.getGlobal()) {
1618       this.emitScalar('<global>');
1619     } else if (value instanceof jasmine.Matchers.Any) {
1620       this.emitScalar(value.toString());
1621     } else if (typeof value === 'string') {
1622       this.emitString(value);
1623     } else if (jasmine.isSpy(value)) {
1624       this.emitScalar("spy on " + value.identity);
1625     } else if (value instanceof RegExp) {
1626       this.emitScalar(value.toString());
1627     } else if (typeof value === 'function') {
1628       this.emitScalar('Function');
1629     } else if (typeof value.nodeType === 'number') {
1630       this.emitScalar('HTMLNode');
1631     } else if (value instanceof Date) {
1632       this.emitScalar('Date(' + value + ')');
1633     } else if (value.__Jasmine_been_here_before__) {
1634       this.emitScalar('<circular reference: ' + (jasmine.isArray_(value) ? 'Array' : 'Object') + '>');
1635     } else if (jasmine.isArray_(value) || typeof value == 'object') {
1636       value.__Jasmine_been_here_before__ = true;
1637       if (jasmine.isArray_(value)) {
1638         this.emitArray(value);
1639       } else {
1640         this.emitObject(value);
1641       }
1642       delete value.__Jasmine_been_here_before__;
1643     } else {
1644       this.emitScalar(value.toString());
1645     }
1646   } finally {
1647     this.ppNestLevel_--;
1648   }
1649 };
1650
1651 jasmine.PrettyPrinter.prototype.iterateObject = function(obj, fn) {
1652   for (var property in obj) {
1653     if (property == '__Jasmine_been_here_before__') continue;
1654     fn(property, obj.__lookupGetter__ ? (obj.__lookupGetter__(property) !== jasmine.undefined && 
1655                                          obj.__lookupGetter__(property) !== null) : false);
1656   }
1657 };
1658
1659 jasmine.PrettyPrinter.prototype.emitArray = jasmine.unimplementedMethod_;
1660 jasmine.PrettyPrinter.prototype.emitObject = jasmine.unimplementedMethod_;
1661 jasmine.PrettyPrinter.prototype.emitScalar = jasmine.unimplementedMethod_;
1662 jasmine.PrettyPrinter.prototype.emitString = jasmine.unimplementedMethod_;
1663
1664 jasmine.StringPrettyPrinter = function() {
1665   jasmine.PrettyPrinter.call(this);
1666
1667   this.string = '';
1668 };
1669 jasmine.util.inherit(jasmine.StringPrettyPrinter, jasmine.PrettyPrinter);
1670
1671 jasmine.StringPrettyPrinter.prototype.emitScalar = function(value) {
1672   this.append(value);
1673 };
1674
1675 jasmine.StringPrettyPrinter.prototype.emitString = function(value) {
1676   this.append("'" + value + "'");
1677 };
1678
1679 jasmine.StringPrettyPrinter.prototype.emitArray = function(array) {
1680   this.append('[ ');
1681   for (var i = 0; i < array.length; i++) {
1682     if (i > 0) {
1683       this.append(', ');
1684     }
1685     this.format(array[i]);
1686   }
1687   this.append(' ]');
1688 };
1689
1690 jasmine.StringPrettyPrinter.prototype.emitObject = function(obj) {
1691   var self = this;
1692   this.append('{ ');
1693   var first = true;
1694
1695   this.iterateObject(obj, function(property, isGetter) {
1696     if (first) {
1697       first = false;
1698     } else {
1699       self.append(', ');
1700     }
1701
1702     self.append(property);
1703     self.append(' : ');
1704     if (isGetter) {
1705       self.append('<getter>');
1706     } else {
1707       self.format(obj[property]);
1708     }
1709   });
1710
1711   this.append(' }');
1712 };
1713
1714 jasmine.StringPrettyPrinter.prototype.append = function(value) {
1715   this.string += value;
1716 };
1717 jasmine.Queue = function(env) {
1718   this.env = env;
1719   this.blocks = [];
1720   this.running = false;
1721   this.index = 0;
1722   this.offset = 0;
1723   this.abort = false;
1724 };
1725
1726 jasmine.Queue.prototype.addBefore = function(block) {
1727   this.blocks.unshift(block);
1728 };
1729
1730 jasmine.Queue.prototype.add = function(block) {
1731   this.blocks.push(block);
1732 };
1733
1734 jasmine.Queue.prototype.insertNext = function(block) {
1735   this.blocks.splice((this.index + this.offset + 1), 0, block);
1736   this.offset++;
1737 };
1738
1739 jasmine.Queue.prototype.start = function(onComplete) {
1740   this.running = true;
1741   this.onComplete = onComplete;
1742   this.next_();
1743 };
1744
1745 jasmine.Queue.prototype.isRunning = function() {
1746   return this.running;
1747 };
1748
1749 jasmine.Queue.LOOP_DONT_RECURSE = true;
1750
1751 jasmine.Queue.prototype.next_ = function() {
1752   var self = this;
1753   var goAgain = true;
1754
1755   while (goAgain) {
1756     goAgain = false;
1757     
1758     if (self.index < self.blocks.length && !this.abort) {
1759       var calledSynchronously = true;
1760       var completedSynchronously = false;
1761
1762       var onComplete = function () {
1763         if (jasmine.Queue.LOOP_DONT_RECURSE && calledSynchronously) {
1764           completedSynchronously = true;
1765           return;
1766         }
1767
1768         if (self.blocks[self.index].abort) {
1769           self.abort = true;
1770         }
1771
1772         self.offset = 0;
1773         self.index++;
1774
1775         var now = new Date().getTime();
1776         if (self.env.updateInterval && now - self.env.lastUpdate > self.env.updateInterval) {
1777           self.env.lastUpdate = now;
1778           self.env.setTimeout(function() {
1779             self.next_();
1780           }, 0);
1781         } else {
1782           if (jasmine.Queue.LOOP_DONT_RECURSE && completedSynchronously) {
1783             goAgain = true;
1784           } else {
1785             self.next_();
1786           }
1787         }
1788       };
1789       self.blocks[self.index].execute(onComplete);
1790
1791       calledSynchronously = false;
1792       if (completedSynchronously) {
1793         onComplete();
1794       }
1795       
1796     } else {
1797       self.running = false;
1798       if (self.onComplete) {
1799         self.onComplete();
1800       }
1801     }
1802   }
1803 };
1804
1805 jasmine.Queue.prototype.results = function() {
1806   var results = new jasmine.NestedResults();
1807   for (var i = 0; i < this.blocks.length; i++) {
1808     if (this.blocks[i].results) {
1809       results.addResult(this.blocks[i].results());
1810     }
1811   }
1812   return results;
1813 };
1814
1815
1816 /**
1817  * Runner
1818  *
1819  * @constructor
1820  * @param {jasmine.Env} env
1821  */
1822 jasmine.Runner = function(env) {
1823   var self = this;
1824   self.env = env;
1825   self.queue = new jasmine.Queue(env);
1826   self.before_ = [];
1827   self.after_ = [];
1828   self.suites_ = [];
1829 };
1830
1831 jasmine.Runner.prototype.execute = function() {
1832   var self = this;
1833   if (self.env.reporter.reportRunnerStarting) {
1834     self.env.reporter.reportRunnerStarting(this);
1835   }
1836   self.queue.start(function () {
1837     self.finishCallback();
1838   });
1839 };
1840
1841 jasmine.Runner.prototype.beforeEach = function(beforeEachFunction) {
1842   beforeEachFunction.typeName = 'beforeEach';
1843   this.before_.splice(0,0,beforeEachFunction);
1844 };
1845
1846 jasmine.Runner.prototype.afterEach = function(afterEachFunction) {
1847   afterEachFunction.typeName = 'afterEach';
1848   this.after_.splice(0,0,afterEachFunction);
1849 };
1850
1851
1852 jasmine.Runner.prototype.finishCallback = function() {
1853   this.env.reporter.reportRunnerResults(this);
1854 };
1855
1856 jasmine.Runner.prototype.addSuite = function(suite) {
1857   this.suites_.push(suite);
1858 };
1859
1860 jasmine.Runner.prototype.add = function(block) {
1861   if (block instanceof jasmine.Suite) {
1862     this.addSuite(block);
1863   }
1864   this.queue.add(block);
1865 };
1866
1867 jasmine.Runner.prototype.specs = function () {
1868   var suites = this.suites();
1869   var specs = [];
1870   for (var i = 0; i < suites.length; i++) {
1871     specs = specs.concat(suites[i].specs());
1872   }
1873   return specs;
1874 };
1875
1876 jasmine.Runner.prototype.suites = function() {
1877   return this.suites_;
1878 };
1879
1880 jasmine.Runner.prototype.topLevelSuites = function() {
1881   var topLevelSuites = [];
1882   for (var i = 0; i < this.suites_.length; i++) {
1883     if (!this.suites_[i].parentSuite) {
1884       topLevelSuites.push(this.suites_[i]);
1885     }
1886   }
1887   return topLevelSuites;
1888 };
1889
1890 jasmine.Runner.prototype.results = function() {
1891   return this.queue.results();
1892 };
1893 /**
1894  * Internal representation of a Jasmine specification, or test.
1895  *
1896  * @constructor
1897  * @param {jasmine.Env} env
1898  * @param {jasmine.Suite} suite
1899  * @param {String} description
1900  */
1901 jasmine.Spec = function(env, suite, description) {
1902   if (!env) {
1903     throw new Error('jasmine.Env() required');
1904   }
1905   if (!suite) {
1906     throw new Error('jasmine.Suite() required');
1907   }
1908   var spec = this;
1909   spec.id = env.nextSpecId ? env.nextSpecId() : null;
1910   spec.env = env;
1911   spec.suite = suite;
1912   spec.description = description;
1913   spec.queue = new jasmine.Queue(env);
1914
1915   spec.afterCallbacks = [];
1916   spec.spies_ = [];
1917
1918   spec.results_ = new jasmine.NestedResults();
1919   spec.results_.description = description;
1920   spec.matchersClass = null;
1921 };
1922
1923 jasmine.Spec.prototype.getFullName = function() {
1924   return this.suite.getFullName() + ' ' + this.description + '.';
1925 };
1926
1927
1928 jasmine.Spec.prototype.results = function() {
1929   return this.results_;
1930 };
1931
1932 /**
1933  * All parameters are pretty-printed and concatenated together, then written to the spec's output.
1934  *
1935  * Be careful not to leave calls to <code>jasmine.log</code> in production code.
1936  */
1937 jasmine.Spec.prototype.log = function() {
1938   return this.results_.log(arguments);
1939 };
1940
1941 jasmine.Spec.prototype.runs = function (func) {
1942   var block = new jasmine.Block(this.env, func, this);
1943   this.addToQueue(block);
1944   return this;
1945 };
1946
1947 jasmine.Spec.prototype.addToQueue = function (block) {
1948   if (this.queue.isRunning()) {
1949     this.queue.insertNext(block);
1950   } else {
1951     this.queue.add(block);
1952   }
1953 };
1954
1955 /**
1956  * @param {jasmine.ExpectationResult} result
1957  */
1958 jasmine.Spec.prototype.addMatcherResult = function(result) {
1959   this.results_.addResult(result);
1960 };
1961
1962 jasmine.Spec.prototype.expect = function(actual) {
1963   var positive = new (this.getMatchersClass_())(this.env, actual, this);
1964   positive.not = new (this.getMatchersClass_())(this.env, actual, this, true);
1965   return positive;
1966 };
1967
1968 /**
1969  * Waits a fixed time period before moving to the next block.
1970  *
1971  * @deprecated Use waitsFor() instead
1972  * @param {Number} timeout milliseconds to wait
1973  */
1974 jasmine.Spec.prototype.waits = function(timeout) {
1975   var waitsFunc = new jasmine.WaitsBlock(this.env, timeout, this);
1976   this.addToQueue(waitsFunc);
1977   return this;
1978 };
1979
1980 /**
1981  * Waits for the latchFunction to return true before proceeding to the next block.
1982  *
1983  * @param {Function} latchFunction
1984  * @param {String} optional_timeoutMessage
1985  * @param {Number} optional_timeout
1986  */
1987 jasmine.Spec.prototype.waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) {
1988   var latchFunction_ = null;
1989   var optional_timeoutMessage_ = null;
1990   var optional_timeout_ = null;
1991
1992   for (var i = 0; i < arguments.length; i++) {
1993     var arg = arguments[i];
1994     switch (typeof arg) {
1995       case 'function':
1996         latchFunction_ = arg;
1997         break;
1998       case 'string':
1999         optional_timeoutMessage_ = arg;
2000         break;
2001       case 'number':
2002         optional_timeout_ = arg;
2003         break;
2004     }
2005   }
2006
2007   var waitsForFunc = new jasmine.WaitsForBlock(this.env, optional_timeout_, latchFunction_, optional_timeoutMessage_, this);
2008   this.addToQueue(waitsForFunc);
2009   return this;
2010 };
2011
2012 jasmine.Spec.prototype.fail = function (e) {
2013   var expectationResult = new jasmine.ExpectationResult({
2014     passed: false,
2015     message: e ? jasmine.util.formatException(e) : 'Exception',
2016     trace: { stack: e.stack }
2017   });
2018   this.results_.addResult(expectationResult);
2019 };
2020
2021 jasmine.Spec.prototype.getMatchersClass_ = function() {
2022   return this.matchersClass || this.env.matchersClass;
2023 };
2024
2025 jasmine.Spec.prototype.addMatchers = function(matchersPrototype) {
2026   var parent = this.getMatchersClass_();
2027   var newMatchersClass = function() {
2028     parent.apply(this, arguments);
2029   };
2030   jasmine.util.inherit(newMatchersClass, parent);
2031   jasmine.Matchers.wrapInto_(matchersPrototype, newMatchersClass);
2032   this.matchersClass = newMatchersClass;
2033 };
2034
2035 jasmine.Spec.prototype.finishCallback = function() {
2036   this.env.reporter.reportSpecResults(this);
2037 };
2038
2039 jasmine.Spec.prototype.finish = function(onComplete) {
2040   this.removeAllSpies();
2041   this.finishCallback();
2042   if (onComplete) {
2043     onComplete();
2044   }
2045 };
2046
2047 jasmine.Spec.prototype.after = function(doAfter) {
2048   if (this.queue.isRunning()) {
2049     this.queue.add(new jasmine.Block(this.env, doAfter, this));
2050   } else {
2051     this.afterCallbacks.unshift(doAfter);
2052   }
2053 };
2054
2055 jasmine.Spec.prototype.execute = function(onComplete) {
2056   var spec = this;
2057   if (!spec.env.specFilter(spec)) {
2058     spec.results_.skipped = true;
2059     spec.finish(onComplete);
2060     return;
2061   }
2062
2063   this.env.reporter.reportSpecStarting(this);
2064
2065   spec.env.currentSpec = spec;
2066
2067   spec.addBeforesAndAftersToQueue();
2068
2069   spec.queue.start(function () {
2070     spec.finish(onComplete);
2071   });
2072 };
2073
2074 jasmine.Spec.prototype.addBeforesAndAftersToQueue = function() {
2075   var runner = this.env.currentRunner();
2076   var i;
2077
2078   for (var suite = this.suite; suite; suite = suite.parentSuite) {
2079     for (i = 0; i < suite.before_.length; i++) {
2080       this.queue.addBefore(new jasmine.Block(this.env, suite.before_[i], this));
2081     }
2082   }
2083   for (i = 0; i < runner.before_.length; i++) {
2084     this.queue.addBefore(new jasmine.Block(this.env, runner.before_[i], this));
2085   }
2086   for (i = 0; i < this.afterCallbacks.length; i++) {
2087     this.queue.add(new jasmine.Block(this.env, this.afterCallbacks[i], this));
2088   }
2089   for (suite = this.suite; suite; suite = suite.parentSuite) {
2090     for (i = 0; i < suite.after_.length; i++) {
2091       this.queue.add(new jasmine.Block(this.env, suite.after_[i], this));
2092     }
2093   }
2094   for (i = 0; i < runner.after_.length; i++) {
2095     this.queue.add(new jasmine.Block(this.env, runner.after_[i], this));
2096   }
2097 };
2098
2099 jasmine.Spec.prototype.explodes = function() {
2100   throw 'explodes function should not have been called';
2101 };
2102
2103 jasmine.Spec.prototype.spyOn = function(obj, methodName, ignoreMethodDoesntExist) {
2104   if (obj == jasmine.undefined) {
2105     throw "spyOn could not find an object to spy upon for " + methodName + "()";
2106   }
2107
2108   if (!ignoreMethodDoesntExist && obj[methodName] === jasmine.undefined) {
2109     throw methodName + '() method does not exist';
2110   }
2111
2112   if (!ignoreMethodDoesntExist && obj[methodName] && obj[methodName].isSpy) {
2113     throw new Error(methodName + ' has already been spied upon');
2114   }
2115
2116   var spyObj = jasmine.createSpy(methodName);
2117
2118   this.spies_.push(spyObj);
2119   spyObj.baseObj = obj;
2120   spyObj.methodName = methodName;
2121   spyObj.originalValue = obj[methodName];
2122
2123   obj[methodName] = spyObj;
2124
2125   return spyObj;
2126 };
2127
2128 jasmine.Spec.prototype.removeAllSpies = function() {
2129   for (var i = 0; i < this.spies_.length; i++) {
2130     var spy = this.spies_[i];
2131     spy.baseObj[spy.methodName] = spy.originalValue;
2132   }
2133   this.spies_ = [];
2134 };
2135
2136 /**
2137  * Internal representation of a Jasmine suite.
2138  *
2139  * @constructor
2140  * @param {jasmine.Env} env
2141  * @param {String} description
2142  * @param {Function} specDefinitions
2143  * @param {jasmine.Suite} parentSuite
2144  */
2145 jasmine.Suite = function(env, description, specDefinitions, parentSuite) {
2146   var self = this;
2147   self.id = env.nextSuiteId ? env.nextSuiteId() : null;
2148   self.description = description;
2149   self.queue = new jasmine.Queue(env);
2150   self.parentSuite = parentSuite;
2151   self.env = env;
2152   self.before_ = [];
2153   self.after_ = [];
2154   self.children_ = [];
2155   self.suites_ = [];
2156   self.specs_ = [];
2157 };
2158
2159 jasmine.Suite.prototype.getFullName = function() {
2160   var fullName = this.description;
2161   for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) {
2162     fullName = parentSuite.description + ' ' + fullName;
2163   }
2164   return fullName;
2165 };
2166
2167 jasmine.Suite.prototype.finish = function(onComplete) {
2168   this.env.reporter.reportSuiteResults(this);
2169   this.finished = true;
2170   if (typeof(onComplete) == 'function') {
2171     onComplete();
2172   }
2173 };
2174
2175 jasmine.Suite.prototype.beforeEach = function(beforeEachFunction) {
2176   beforeEachFunction.typeName = 'beforeEach';
2177   this.before_.unshift(beforeEachFunction);
2178 };
2179
2180 jasmine.Suite.prototype.afterEach = function(afterEachFunction) {
2181   afterEachFunction.typeName = 'afterEach';
2182   this.after_.unshift(afterEachFunction);
2183 };
2184
2185 jasmine.Suite.prototype.results = function() {
2186   return this.queue.results();
2187 };
2188
2189 jasmine.Suite.prototype.add = function(suiteOrSpec) {
2190   this.children_.push(suiteOrSpec);
2191   if (suiteOrSpec instanceof jasmine.Suite) {
2192     this.suites_.push(suiteOrSpec);
2193     this.env.currentRunner().addSuite(suiteOrSpec);
2194   } else {
2195     this.specs_.push(suiteOrSpec);
2196   }
2197   this.queue.add(suiteOrSpec);
2198 };
2199
2200 jasmine.Suite.prototype.specs = function() {
2201   return this.specs_;
2202 };
2203
2204 jasmine.Suite.prototype.suites = function() {
2205   return this.suites_;
2206 };
2207
2208 jasmine.Suite.prototype.children = function() {
2209   return this.children_;
2210 };
2211
2212 jasmine.Suite.prototype.execute = function(onComplete) {
2213   var self = this;
2214   this.queue.start(function () {
2215     self.finish(onComplete);
2216   });
2217 };
2218 jasmine.WaitsBlock = function(env, timeout, spec) {
2219   this.timeout = timeout;
2220   jasmine.Block.call(this, env, null, spec);
2221 };
2222
2223 jasmine.util.inherit(jasmine.WaitsBlock, jasmine.Block);
2224
2225 jasmine.WaitsBlock.prototype.execute = function (onComplete) {
2226   if (jasmine.VERBOSE) {
2227     this.env.reporter.log('>> Jasmine waiting for ' + this.timeout + ' ms...');
2228   }
2229   this.env.setTimeout(function () {
2230     onComplete();
2231   }, this.timeout);
2232 };
2233 /**
2234  * A block which waits for some condition to become true, with timeout.
2235  *
2236  * @constructor
2237  * @extends jasmine.Block
2238  * @param {jasmine.Env} env The Jasmine environment.
2239  * @param {Number} timeout The maximum time in milliseconds to wait for the condition to become true.
2240  * @param {Function} latchFunction A function which returns true when the desired condition has been met.
2241  * @param {String} message The message to display if the desired condition hasn't been met within the given time period.
2242  * @param {jasmine.Spec} spec The Jasmine spec.
2243  */
2244 jasmine.WaitsForBlock = function(env, timeout, latchFunction, message, spec) {
2245   this.timeout = timeout || env.defaultTimeoutInterval;
2246   this.latchFunction = latchFunction;
2247   this.message = message;
2248   this.totalTimeSpentWaitingForLatch = 0;
2249   jasmine.Block.call(this, env, null, spec);
2250 };
2251 jasmine.util.inherit(jasmine.WaitsForBlock, jasmine.Block);
2252
2253 jasmine.WaitsForBlock.TIMEOUT_INCREMENT = 10;
2254
2255 jasmine.WaitsForBlock.prototype.execute = function(onComplete) {
2256   if (jasmine.VERBOSE) {
2257     this.env.reporter.log('>> Jasmine waiting for ' + (this.message || 'something to happen'));
2258   }
2259   var latchFunctionResult;
2260   try {
2261     latchFunctionResult = this.latchFunction.apply(this.spec);
2262   } catch (e) {
2263     this.spec.fail(e);
2264     onComplete();
2265     return;
2266   }
2267
2268   if (latchFunctionResult) {
2269     onComplete();
2270   } else if (this.totalTimeSpentWaitingForLatch >= this.timeout) {
2271     var message = 'timed out after ' + this.timeout + ' msec waiting for ' + (this.message || 'something to happen');
2272     this.spec.fail({
2273       name: 'timeout',
2274       message: message
2275     });
2276
2277     this.abort = true;
2278     onComplete();
2279   } else {
2280     this.totalTimeSpentWaitingForLatch += jasmine.WaitsForBlock.TIMEOUT_INCREMENT;
2281     var self = this;
2282     this.env.setTimeout(function() {
2283       self.execute(onComplete);
2284     }, jasmine.WaitsForBlock.TIMEOUT_INCREMENT);
2285   }
2286 };
2287 // Mock setTimeout, clearTimeout
2288 // Contributed by Pivotal Computer Systems, www.pivotalsf.com
2289
2290 jasmine.FakeTimer = function() {
2291   this.reset();
2292
2293   var self = this;
2294   self.setTimeout = function(funcToCall, millis) {
2295     self.timeoutsMade++;
2296     self.scheduleFunction(self.timeoutsMade, funcToCall, millis, false);
2297     return self.timeoutsMade;
2298   };
2299
2300   self.setInterval = function(funcToCall, millis) {
2301     self.timeoutsMade++;
2302     self.scheduleFunction(self.timeoutsMade, funcToCall, millis, true);
2303     return self.timeoutsMade;
2304   };
2305
2306   self.clearTimeout = function(timeoutKey) {
2307     self.scheduledFunctions[timeoutKey] = jasmine.undefined;
2308   };
2309
2310   self.clearInterval = function(timeoutKey) {
2311     self.scheduledFunctions[timeoutKey] = jasmine.undefined;
2312   };
2313
2314 };
2315
2316 jasmine.FakeTimer.prototype.reset = function() {
2317   this.timeoutsMade = 0;
2318   this.scheduledFunctions = {};
2319   this.nowMillis = 0;
2320 };
2321
2322 jasmine.FakeTimer.prototype.tick = function(millis) {
2323   var oldMillis = this.nowMillis;
2324   var newMillis = oldMillis + millis;
2325   this.runFunctionsWithinRange(oldMillis, newMillis);
2326   this.nowMillis = newMillis;
2327 };
2328
2329 jasmine.FakeTimer.prototype.runFunctionsWithinRange = function(oldMillis, nowMillis) {
2330   var scheduledFunc;
2331   var funcsToRun = [];
2332   for (var timeoutKey in this.scheduledFunctions) {
2333     scheduledFunc = this.scheduledFunctions[timeoutKey];
2334     if (scheduledFunc != jasmine.undefined &&
2335         scheduledFunc.runAtMillis >= oldMillis &&
2336         scheduledFunc.runAtMillis <= nowMillis) {
2337       funcsToRun.push(scheduledFunc);
2338       this.scheduledFunctions[timeoutKey] = jasmine.undefined;
2339     }
2340   }
2341
2342   if (funcsToRun.length > 0) {
2343     funcsToRun.sort(function(a, b) {
2344       return a.runAtMillis - b.runAtMillis;
2345     });
2346     for (var i = 0; i < funcsToRun.length; ++i) {
2347       try {
2348         var funcToRun = funcsToRun[i];
2349         this.nowMillis = funcToRun.runAtMillis;
2350         funcToRun.funcToCall();
2351         if (funcToRun.recurring) {
2352           this.scheduleFunction(funcToRun.timeoutKey,
2353               funcToRun.funcToCall,
2354               funcToRun.millis,
2355               true);
2356         }
2357       } catch(e) {
2358       }
2359     }
2360     this.runFunctionsWithinRange(oldMillis, nowMillis);
2361   }
2362 };
2363
2364 jasmine.FakeTimer.prototype.scheduleFunction = function(timeoutKey, funcToCall, millis, recurring) {
2365   this.scheduledFunctions[timeoutKey] = {
2366     runAtMillis: this.nowMillis + millis,
2367     funcToCall: funcToCall,
2368     recurring: recurring,
2369     timeoutKey: timeoutKey,
2370     millis: millis
2371   };
2372 };
2373
2374 /**
2375  * @namespace
2376  */
2377 jasmine.Clock = {
2378   defaultFakeTimer: new jasmine.FakeTimer(),
2379
2380   reset: function() {
2381     jasmine.Clock.assertInstalled();
2382     jasmine.Clock.defaultFakeTimer.reset();
2383   },
2384
2385   tick: function(millis) {
2386     jasmine.Clock.assertInstalled();
2387     jasmine.Clock.defaultFakeTimer.tick(millis);
2388   },
2389
2390   runFunctionsWithinRange: function(oldMillis, nowMillis) {
2391     jasmine.Clock.defaultFakeTimer.runFunctionsWithinRange(oldMillis, nowMillis);
2392   },
2393
2394   scheduleFunction: function(timeoutKey, funcToCall, millis, recurring) {
2395     jasmine.Clock.defaultFakeTimer.scheduleFunction(timeoutKey, funcToCall, millis, recurring);
2396   },
2397
2398   useMock: function() {
2399     if (!jasmine.Clock.isInstalled()) {
2400       var spec = jasmine.getEnv().currentSpec;
2401       spec.after(jasmine.Clock.uninstallMock);
2402
2403       jasmine.Clock.installMock();
2404     }
2405   },
2406
2407   installMock: function() {
2408     jasmine.Clock.installed = jasmine.Clock.defaultFakeTimer;
2409   },
2410
2411   uninstallMock: function() {
2412     jasmine.Clock.assertInstalled();
2413     jasmine.Clock.installed = jasmine.Clock.real;
2414   },
2415
2416   real: {
2417     setTimeout: jasmine.getGlobal().setTimeout,
2418     clearTimeout: jasmine.getGlobal().clearTimeout,
2419     setInterval: jasmine.getGlobal().setInterval,
2420     clearInterval: jasmine.getGlobal().clearInterval
2421   },
2422
2423   assertInstalled: function() {
2424     if (!jasmine.Clock.isInstalled()) {
2425       throw new Error("Mock clock is not installed, use jasmine.Clock.useMock()");
2426     }
2427   },
2428
2429   isInstalled: function() {
2430     return jasmine.Clock.installed == jasmine.Clock.defaultFakeTimer;
2431   },
2432
2433   installed: null
2434 };
2435 jasmine.Clock.installed = jasmine.Clock.real;
2436
2437 //else for IE support
2438 jasmine.getGlobal().setTimeout = function(funcToCall, millis) {
2439   if (jasmine.Clock.installed.setTimeout.apply) {
2440     return jasmine.Clock.installed.setTimeout.apply(this, arguments);
2441   } else {
2442     return jasmine.Clock.installed.setTimeout(funcToCall, millis);
2443   }
2444 };
2445
2446 jasmine.getGlobal().setInterval = function(funcToCall, millis) {
2447   if (jasmine.Clock.installed.setInterval.apply) {
2448     return jasmine.Clock.installed.setInterval.apply(this, arguments);
2449   } else {
2450     return jasmine.Clock.installed.setInterval(funcToCall, millis);
2451   }
2452 };
2453
2454 jasmine.getGlobal().clearTimeout = function(timeoutKey) {
2455   if (jasmine.Clock.installed.clearTimeout.apply) {
2456     return jasmine.Clock.installed.clearTimeout.apply(this, arguments);
2457   } else {
2458     return jasmine.Clock.installed.clearTimeout(timeoutKey);
2459   }
2460 };
2461
2462 jasmine.getGlobal().clearInterval = function(timeoutKey) {
2463   if (jasmine.Clock.installed.clearTimeout.apply) {
2464     return jasmine.Clock.installed.clearInterval.apply(this, arguments);
2465   } else {
2466     return jasmine.Clock.installed.clearInterval(timeoutKey);
2467   }
2468 };
2469
2470 jasmine.version_= {
2471   "major": 1,
2472   "minor": 1,
2473   "build": 0,
2474   "revision": 1308187385,
2475   "rc": 1
2476 }