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