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