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": 1256686889 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 /** 970 * @constructor 971 * @param {jasmine.Env} env 972 * @param actual 973 * @param {jasmine.NestedResults} results 974 */ 975 jasmine.Matchers = function(env, actual, results) { 976 this.env = env; 977 this.actual = actual; 978 this.passing_message = 'Passed.'; 979 this.results_ = results || new jasmine.NestedResults(); 980 }; 981 982 jasmine.Matchers.pp = function(str) { 983 return jasmine.util.htmlEscape(jasmine.pp(str)); 984 }; 985 986 jasmine.Matchers.prototype.results = function() { 987 return this.results_; 988 }; 989 990 jasmine.Matchers.prototype.report = function(result, failing_message, details) { 991 this.results_.addResult(new jasmine.ExpectationResult(result, result ? this.passing_message : failing_message, details)); 992 return result; 993 }; 994 995 /** 996 * Matcher that compares the actual to the expected using ===. 997 * 998 * @param expected 999 */ 1000 jasmine.Matchers.prototype.toBe = function(expected) { 1001 return this.report(this.actual === expected, 'Expected<br /><br />' + jasmine.Matchers.pp(expected) 1002 + '<br /><br />to be the same object as<br /><br />' + jasmine.Matchers.pp(this.actual) 1003 + '<br />'); 1004 }; 1005 1006 /** 1007 * Matcher that compares the actual to the expected using !== 1008 * @param expected 1009 */ 1010 jasmine.Matchers.prototype.toNotBe = function(expected) { 1011 return this.report(this.actual !== expected, 'Expected<br /><br />' + jasmine.Matchers.pp(expected) 1012 + '<br /><br />to be a different object from actual, but they were the same.'); 1013 }; 1014 1015 /** 1016 * Matcher that compares the actual to the expected using common sense equality. Handles Objects, Arrays, etc. 1017 * 1018 * @param expected 1019 */ 1020 jasmine.Matchers.prototype.toEqual = function(expected) { 1021 var mismatchKeys = []; 1022 var mismatchValues = []; 1023 1024 var formatMismatches = function(name, array) { 1025 if (array.length == 0) return ''; 1026 var errorOutput = '<br /><br />Different ' + name + ':<br />'; 1027 for (var i = 0; i < array.length; i++) { 1028 errorOutput += array[i] + '<br />'; 1029 } 1030 return errorOutput; 1031 }; 1032 1033 return this.report(this.env.equals_(this.actual, expected, mismatchKeys, mismatchValues), 1034 'Expected<br /><br />' + jasmine.Matchers.pp(expected) 1035 + '<br /><br />but got<br /><br />' + jasmine.Matchers.pp(this.actual) 1036 + '<br />' 1037 + formatMismatches('Keys', mismatchKeys) 1038 + formatMismatches('Values', mismatchValues), { 1039 matcherName: 'toEqual', expected: expected, actual: this.actual 1040 }); 1041 }; 1042 1043 /** 1044 * Matcher that compares the actual to the expected using the ! of jasmine.Matchers.toEqual 1045 * @param expected 1046 */ 1047 jasmine.Matchers.prototype.toNotEqual = function(expected) { 1048 return this.report(!this.env.equals_(this.actual, expected), 1049 'Expected ' + jasmine.Matchers.pp(expected) + ' to not equal ' + jasmine.Matchers.pp(this.actual) + ', but it does.'); 1050 }; 1051 1052 /** 1053 * Matcher that compares the actual to the expected using a regular expression. Constructs a RegExp, so takes 1054 * a pattern or a String. 1055 * 1056 * @param reg_exp 1057 */ 1058 jasmine.Matchers.prototype.toMatch = function(reg_exp) { 1059 return this.report((new RegExp(reg_exp).test(this.actual)), 1060 'Expected ' + jasmine.Matchers.pp(this.actual) + ' to match ' + reg_exp + '.'); 1061 }; 1062 1063 /** 1064 * Matcher that compares the actual to the expected using the boolean inverse of jasmine.Matchers.toMatch 1065 * @param reg_exp 1066 */ 1067 jasmine.Matchers.prototype.toNotMatch = function(reg_exp) { 1068 return this.report((!new RegExp(reg_exp).test(this.actual)), 1069 'Expected ' + jasmine.Matchers.pp(this.actual) + ' to not match ' + reg_exp + '.'); 1070 }; 1071 1072 /** 1073 * Matcher that compares the acutal to undefined. 1074 */ 1075 jasmine.Matchers.prototype.toBeDefined = function() { 1076 return this.report((this.actual !== undefined), 1077 'Expected a value to be defined but it was undefined.'); 1078 }; 1079 1080 /** 1081 * Matcher that compares the actual to null. 1082 * 1083 */ 1084 jasmine.Matchers.prototype.toBeNull = function() { 1085 return this.report((this.actual === null), 1086 'Expected a value to be null but it was ' + jasmine.Matchers.pp(this.actual) + '.'); 1087 }; 1088 1089 /** 1090 * Matcher that boolean not-nots the actual. 1091 */ 1092 jasmine.Matchers.prototype.toBeTruthy = function() { 1093 return this.report(!!this.actual, 1094 'Expected a value to be truthy but it was ' + jasmine.Matchers.pp(this.actual) + '.'); 1095 }; 1096 1097 /** 1098 * Matcher that boolean nots the actual. 1099 */ 1100 jasmine.Matchers.prototype.toBeFalsy = function() { 1101 return this.report(!this.actual, 1102 'Expected a value to be falsy but it was ' + jasmine.Matchers.pp(this.actual) + '.'); 1103 }; 1104 1105 /** 1106 * Matcher that checks to see if the acutal, a Jasmine spy, was called. 1107 */ 1108 jasmine.Matchers.prototype.wasCalled = function() { 1109 if (!this.actual || !this.actual.isSpy) { 1110 return this.report(false, 'Expected a spy, but got ' + jasmine.Matchers.pp(this.actual) + '.'); 1111 } 1112 if (arguments.length > 0) { 1113 return this.report(false, 'wasCalled matcher does not take arguments'); 1114 } 1115 return this.report((this.actual.wasCalled), 1116 'Expected spy "' + this.actual.identity + '" to have been called, but it was not.'); 1117 }; 1118 1119 /** 1120 * Matcher that checks to see if the acutal, a Jasmine spy, was not called. 1121 */ 1122 jasmine.Matchers.prototype.wasNotCalled = function() { 1123 if (!this.actual || !this.actual.isSpy) { 1124 return this.report(false, 'Expected a spy, but got ' + jasmine.Matchers.pp(this.actual) + '.'); 1125 } 1126 return this.report((!this.actual.wasCalled), 1127 'Expected spy "' + this.actual.identity + '" to not have been called, but it was.'); 1128 }; 1129 1130 /** 1131 * Matcher that checks to see if the acutal, a Jasmine spy, was called with a set of parameters. 1132 * 1133 * @example 1134 * 1135 */ 1136 jasmine.Matchers.prototype.wasCalledWith = function() { 1137 if (!this.actual || !this.actual.isSpy) { 1138 return this.report(false, 'Expected a spy, but got ' + jasmine.Matchers.pp(this.actual) + '.', { 1139 matcherName: 'wasCalledWith' 1140 }); 1141 } 1142 1143 var args = jasmine.util.argsToArray(arguments); 1144 1145 return this.report(this.env.contains_(this.actual.argsForCall, args), 1146 'Expected ' + jasmine.Matchers.pp(this.actual.argsForCall) + ' to contain ' + jasmine.Matchers.pp(args) + ', but it does not.', { 1147 matcherName: 'wasCalledWith', expected: args, actual: this.actual.argsForCall 1148 }); 1149 }; 1150 1151 /** 1152 * Matcher that checks that the expected item is an element in the actual Array. 1153 * 1154 * @param {Object} item 1155 */ 1156 jasmine.Matchers.prototype.toContain = function(item) { 1157 return this.report(this.env.contains_(this.actual, item), 1158 'Expected ' + jasmine.Matchers.pp(this.actual) + ' to contain ' + jasmine.Matchers.pp(item) + ', but it does not.', { 1159 matcherName: 'toContain', expected: item, actual: this.actual 1160 }); 1161 }; 1162 1163 /** 1164 * Matcher that checks that the expected item is NOT an element in the actual Array. 1165 * 1166 * @param {Object} item 1167 */ 1168 jasmine.Matchers.prototype.toNotContain = function(item) { 1169 return this.report(!this.env.contains_(this.actual, item), 1170 'Expected ' + jasmine.Matchers.pp(this.actual) + ' not to contain ' + jasmine.Matchers.pp(item) + ', but it does.'); 1171 }; 1172 1173 jasmine.Matchers.prototype.toBeLessThan = function(expected) { 1174 return this.report(this.actual < expected, 1175 'Expected ' + jasmine.Matchers.pp(this.actual) + ' to be less than ' + jasmine.Matchers.pp(expected) + ', but it was not.'); 1176 }; 1177 1178 jasmine.Matchers.prototype.toBeGreaterThan = function(expected) { 1179 return this.report(this.actual > expected, 1180 'Expected ' + jasmine.Matchers.pp(this.actual) + ' to be greater than ' + jasmine.Matchers.pp(expected) + ', but it was not.'); 1181 }; 1182 1183 /** 1184 * Matcher that checks that the expected exception was thrown by the actual. 1185 * 1186 * @param {String} expectedException 1187 */ 1188 jasmine.Matchers.prototype.toThrow = function(expectedException) { 1189 var exception = null; 1190 try { 1191 this.actual(); 1192 } catch (e) { 1193 exception = e; 1194 } 1195 if (expectedException !== undefined) { 1196 if (exception == null) { 1197 return this.report(false, "Expected function to throw " + jasmine.Matchers.pp(expectedException) + ", but it did not."); 1198 } 1199 return this.report( 1200 this.env.equals_( 1201 exception.message || exception, 1202 expectedException.message || expectedException), 1203 "Expected function to throw " + jasmine.Matchers.pp(expectedException) + ", but it threw " + jasmine.Matchers.pp(exception) + "."); 1204 } else { 1205 return this.report(exception != null, "Expected function to throw an exception, but it did not."); 1206 } 1207 }; 1208 1209 jasmine.Matchers.Any = function(expectedClass) { 1210 this.expectedClass = expectedClass; 1211 }; 1212 1213 jasmine.Matchers.Any.prototype.matches = function(other) { 1214 if (this.expectedClass == String) { 1215 return typeof other == 'string' || other instanceof String; 1216 } 1217 1218 if (this.expectedClass == Number) { 1219 return typeof other == 'number' || other instanceof Number; 1220 } 1221 1222 if (this.expectedClass == Function) { 1223 return typeof other == 'function' || other instanceof Function; 1224 } 1225 1226 if (this.expectedClass == Object) { 1227 return typeof other == 'object'; 1228 } 1229 1230 return other instanceof this.expectedClass; 1231 }; 1232 1233 jasmine.Matchers.Any.prototype.toString = function() { 1234 return '<jasmine.any(' + this.expectedClass + ')>'; 1235 }; 1236 1237 /** 1238 * @constructor 1239 */ 1240 jasmine.MultiReporter = function() { 1241 this.subReporters_ = []; 1242 }; 1243 jasmine.util.inherit(jasmine.MultiReporter, jasmine.Reporter); 1244 1245 jasmine.MultiReporter.prototype.addReporter = function(reporter) { 1246 this.subReporters_.push(reporter); 1247 }; 1248 1249 (function() { 1250 var functionNames = ["reportRunnerStarting", "reportRunnerResults", "reportSuiteResults", "reportSpecResults", "log"]; 1251 for (var i = 0; i < functionNames.length; i++) { 1252 var functionName = functionNames[i]; 1253 jasmine.MultiReporter.prototype[functionName] = (function(functionName) { 1254 return function() { 1255 for (var j = 0; j < this.subReporters_.length; j++) { 1256 var subReporter = this.subReporters_[j]; 1257 if (subReporter[functionName]) { 1258 subReporter[functionName].apply(subReporter, arguments); 1259 } 1260 } 1261 }; 1262 })(functionName); 1263 } 1264 })(); 1265 /** 1266 * Holds results for a set of Jasmine spec. Allows for the results array to hold another jasmine.NestedResults 1267 * 1268 * @constructor 1269 */ 1270 jasmine.NestedResults = function() { 1271 /** 1272 * The total count of results 1273 */ 1274 this.totalCount = 0; 1275 /** 1276 * Number of passed results 1277 */ 1278 this.passedCount = 0; 1279 /** 1280 * Number of failed results 1281 */ 1282 this.failedCount = 0; 1283 /** 1284 * Was this suite/spec skipped? 1285 */ 1286 this.skipped = false; 1287 /** 1288 * @ignore 1289 */ 1290 this.items_ = []; 1291 }; 1292 1293 /** 1294 * Roll up the result counts. 1295 * 1296 * @param result 1297 */ 1298 jasmine.NestedResults.prototype.rollupCounts = function(result) { 1299 this.totalCount += result.totalCount; 1300 this.passedCount += result.passedCount; 1301 this.failedCount += result.failedCount; 1302 }; 1303 1304 /** 1305 * Tracks a result's message. 1306 * @param message 1307 */ 1308 jasmine.NestedResults.prototype.log = function(message) { 1309 this.items_.push(new jasmine.MessageResult(message)); 1310 }; 1311 1312 /** 1313 * Getter for the results: message & results. 1314 */ 1315 jasmine.NestedResults.prototype.getItems = function() { 1316 return this.items_; 1317 }; 1318 1319 /** 1320 * Adds a result, tracking counts (total, passed, & failed) 1321 * @param {jasmine.ExpectationResult|jasmine.NestedResults} result 1322 */ 1323 jasmine.NestedResults.prototype.addResult = function(result) { 1324 if (result.type != 'MessageResult') { 1325 if (result.items_) { 1326 this.rollupCounts(result); 1327 } else { 1328 this.totalCount++; 1329 if (result.passed()) { 1330 this.passedCount++; 1331 } else { 1332 this.failedCount++; 1333 } 1334 } 1335 } 1336 this.items_.push(result); 1337 }; 1338 1339 /** 1340 * @returns {Boolean} True if <b>everything</b> below passed 1341 */ 1342 jasmine.NestedResults.prototype.passed = function() { 1343 return this.passedCount === this.totalCount; 1344 }; 1345 /** 1346 * Base class for pretty printing for expectation results. 1347 */ 1348 jasmine.PrettyPrinter = function() { 1349 this.ppNestLevel_ = 0; 1350 }; 1351 1352 /** 1353 * Formats a value in a nice, human-readable string. 1354 * 1355 * @param value 1356 * @returns {String} 1357 */ 1358 jasmine.PrettyPrinter.prototype.format = function(value) { 1359 if (this.ppNestLevel_ > 40) { 1360 // return '(jasmine.pp nested too deeply!)'; 1361 throw new Error('jasmine.PrettyPrinter: format() nested too deeply!'); 1362 } 1363 1364 this.ppNestLevel_++; 1365 try { 1366 if (value === undefined) { 1367 this.emitScalar('undefined'); 1368 } else if (value === null) { 1369 this.emitScalar('null'); 1370 } else if (value.navigator && value.frames && value.setTimeout) { 1371 this.emitScalar('<window>'); 1372 } else if (value instanceof jasmine.Matchers.Any) { 1373 this.emitScalar(value.toString()); 1374 } else if (typeof value === 'string') { 1375 this.emitString(value); 1376 } else if (typeof value === 'function') { 1377 this.emitScalar('Function'); 1378 } else if (typeof value.nodeType === 'number') { 1379 this.emitScalar('HTMLNode'); 1380 } else if (value instanceof Date) { 1381 this.emitScalar('Date(' + value + ')'); 1382 } else if (value.__Jasmine_been_here_before__) { 1383 this.emitScalar('<circular reference: ' + (jasmine.isArray_(value) ? 'Array' : 'Object') + '>'); 1384 } else if (jasmine.isArray_(value) || typeof value == 'object') { 1385 value.__Jasmine_been_here_before__ = true; 1386 if (jasmine.isArray_(value)) { 1387 this.emitArray(value); 1388 } else { 1389 this.emitObject(value); 1390 } 1391 delete value.__Jasmine_been_here_before__; 1392 } else { 1393 this.emitScalar(value.toString()); 1394 } 1395 } finally { 1396 this.ppNestLevel_--; 1397 } 1398 }; 1399 1400 jasmine.PrettyPrinter.prototype.iterateObject = function(obj, fn) { 1401 for (var property in obj) { 1402 if (property == '__Jasmine_been_here_before__') continue; 1403 fn(property, obj.__lookupGetter__ ? (obj.__lookupGetter__(property) != null) : false); 1404 } 1405 }; 1406 1407 jasmine.PrettyPrinter.prototype.emitArray = jasmine.unimplementedMethod_; 1408 jasmine.PrettyPrinter.prototype.emitObject = jasmine.unimplementedMethod_; 1409 jasmine.PrettyPrinter.prototype.emitScalar = jasmine.unimplementedMethod_; 1410 jasmine.PrettyPrinter.prototype.emitString = jasmine.unimplementedMethod_; 1411 1412 jasmine.StringPrettyPrinter = function() { 1413 jasmine.PrettyPrinter.call(this); 1414 1415 this.string = ''; 1416 }; 1417 jasmine.util.inherit(jasmine.StringPrettyPrinter, jasmine.PrettyPrinter); 1418 1419 jasmine.StringPrettyPrinter.prototype.emitScalar = function(value) { 1420 this.append(value); 1421 }; 1422 1423 jasmine.StringPrettyPrinter.prototype.emitString = function(value) { 1424 this.append("'" + value + "'"); 1425 }; 1426 1427 jasmine.StringPrettyPrinter.prototype.emitArray = function(array) { 1428 this.append('[ '); 1429 for (var i = 0; i < array.length; i++) { 1430 if (i > 0) { 1431 this.append(', '); 1432 } 1433 this.format(array[i]); 1434 } 1435 this.append(' ]'); 1436 }; 1437 1438 jasmine.StringPrettyPrinter.prototype.emitObject = function(obj) { 1439 var self = this; 1440 this.append('{ '); 1441 var first = true; 1442 1443 this.iterateObject(obj, function(property, isGetter) { 1444 if (first) { 1445 first = false; 1446 } else { 1447 self.append(', '); 1448 } 1449 1450 self.append(property); 1451 self.append(' : '); 1452 if (isGetter) { 1453 self.append('<getter>'); 1454 } else { 1455 self.format(obj[property]); 1456 } 1457 }); 1458 1459 this.append(' }'); 1460 }; 1461 1462 jasmine.StringPrettyPrinter.prototype.append = function(value) { 1463 this.string += value; 1464 }; 1465 jasmine.Queue = function(env) { 1466 this.env = env; 1467 this.blocks = []; 1468 this.running = false; 1469 this.index = 0; 1470 this.offset = 0; 1471 }; 1472 1473 jasmine.Queue.prototype.addBefore = function(block) { 1474 this.blocks.unshift(block); 1475 }; 1476 1477 jasmine.Queue.prototype.add = function(block) { 1478 this.blocks.push(block); 1479 }; 1480 1481 jasmine.Queue.prototype.insertNext = function(block) { 1482 this.blocks.splice((this.index + this.offset + 1), 0, block); 1483 this.offset++; 1484 }; 1485 1486 jasmine.Queue.prototype.start = function(onComplete) { 1487 this.running = true; 1488 this.onComplete = onComplete; 1489 this.next_(); 1490 }; 1491 1492 jasmine.Queue.prototype.isRunning = function() { 1493 return this.running; 1494 }; 1495 1496 jasmine.Queue.LOOP_DONT_RECURSE = true; 1497 1498 jasmine.Queue.prototype.next_ = function() { 1499 var self = this; 1500 var goAgain = true; 1501 1502 while (goAgain) { 1503 goAgain = false; 1504 1505 if (self.index < self.blocks.length) { 1506 var calledSynchronously = true; 1507 var completedSynchronously = false; 1508 1509 var onComplete = function () { 1510 if (jasmine.Queue.LOOP_DONT_RECURSE && calledSynchronously) { 1511 completedSynchronously = true; 1512 return; 1513 } 1514 1515 self.offset = 0; 1516 self.index++; 1517 1518 var now = new Date().getTime(); 1519 if (self.env.updateInterval && now - self.env.lastUpdate > self.env.updateInterval) { 1520 self.env.lastUpdate = now; 1521 self.env.setTimeout(function() { 1522 self.next_(); 1523 }, 0); 1524 } else { 1525 if (jasmine.Queue.LOOP_DONT_RECURSE && completedSynchronously) { 1526 goAgain = true; 1527 } else { 1528 self.next_(); 1529 } 1530 } 1531 }; 1532 self.blocks[self.index].execute(onComplete); 1533 1534 calledSynchronously = false; 1535 if (completedSynchronously) { 1536 onComplete(); 1537 } 1538 1539 } else { 1540 self.running = false; 1541 if (self.onComplete) { 1542 self.onComplete(); 1543 } 1544 } 1545 } 1546 }; 1547 1548 jasmine.Queue.prototype.results = function() { 1549 var results = new jasmine.NestedResults(); 1550 for (var i = 0; i < this.blocks.length; i++) { 1551 if (this.blocks[i].results) { 1552 results.addResult(this.blocks[i].results()); 1553 } 1554 } 1555 return results; 1556 }; 1557 1558 1559 /* JasmineReporters.reporter 1560 * Base object that will get called whenever a Spec, Suite, or Runner is done. It is up to 1561 * descendants of this object to do something with the results (see json_reporter.js) 1562 */ 1563 jasmine.Reporters = {}; 1564 1565 jasmine.Reporters.reporter = function(callbacks) { 1566 var that = { 1567 callbacks: callbacks || {}, 1568 1569 doCallback: function(callback, results) { 1570 if (callback) { 1571 callback(results); 1572 } 1573 }, 1574 1575 reportRunnerResults: function(runner) { 1576 that.doCallback(that.callbacks.runnerCallback, runner); 1577 }, 1578 reportSuiteResults: function(suite) { 1579 that.doCallback(that.callbacks.suiteCallback, suite); 1580 }, 1581 reportSpecResults: function(spec) { 1582 that.doCallback(that.callbacks.specCallback, spec); 1583 }, 1584 log: function (str) { 1585 if (console && console.log) console.log(str); 1586 } 1587 }; 1588 1589 return that; 1590 }; 1591 1592 /** 1593 * Runner 1594 * 1595 * @constructor 1596 * @param {jasmine.Env} env 1597 */ 1598 jasmine.Runner = function(env) { 1599 var self = this; 1600 self.env = env; 1601 self.queue = new jasmine.Queue(env); 1602 self.before_ = []; 1603 self.after_ = []; 1604 self.suites_ = []; 1605 }; 1606 1607 jasmine.Runner.prototype.execute = function() { 1608 var self = this; 1609 if (self.env.reporter.reportRunnerStarting) { 1610 self.env.reporter.reportRunnerStarting(this); 1611 } 1612 self.queue.start(function () { 1613 self.finishCallback(); 1614 }); 1615 }; 1616 1617 jasmine.Runner.prototype.beforeEach = function(beforeEachFunction) { 1618 beforeEachFunction.typeName = 'beforeEach'; 1619 this.before_.push(beforeEachFunction); 1620 }; 1621 1622 jasmine.Runner.prototype.afterEach = function(afterEachFunction) { 1623 afterEachFunction.typeName = 'afterEach'; 1624 this.after_.push(afterEachFunction); 1625 }; 1626 1627 1628 jasmine.Runner.prototype.finishCallback = function() { 1629 this.env.reporter.reportRunnerResults(this); 1630 }; 1631 1632 jasmine.Runner.prototype.addSuite = function(suite) { 1633 this.suites_.push(suite); 1634 }; 1635 1636 jasmine.Runner.prototype.add = function(block) { 1637 if (block instanceof jasmine.Suite) { 1638 this.addSuite(block); 1639 } 1640 this.queue.add(block); 1641 }; 1642 1643 jasmine.Runner.prototype.specs = function () { 1644 var suites = this.suites(); 1645 var specs = []; 1646 for (var i = 0; i < suites.length; i++) { 1647 specs = specs.concat(suites[i].specs()); 1648 } 1649 return specs; 1650 }; 1651 1652 1653 jasmine.Runner.prototype.suites = function() { 1654 return this.suites_; 1655 }; 1656 1657 jasmine.Runner.prototype.results = function() { 1658 return this.queue.results(); 1659 }; 1660 /** 1661 * Internal representation of a Jasmine specification, or test. 1662 * 1663 * @constructor 1664 * @param {jasmine.Env} env 1665 * @param {jasmine.Suite} suite 1666 * @param {String} description 1667 */ 1668 jasmine.Spec = function(env, suite, description) { 1669 if (!env) { 1670 throw new Error('jasmine.Env() required'); 1671 } 1672 ; 1673 if (!suite) { 1674 throw new Error('jasmine.Suite() required'); 1675 } 1676 ; 1677 var spec = this; 1678 spec.id = env.nextSpecId ? env.nextSpecId() : null; 1679 spec.env = env; 1680 spec.suite = suite; 1681 spec.description = description; 1682 spec.queue = new jasmine.Queue(env); 1683 1684 spec.afterCallbacks = []; 1685 spec.spies_ = []; 1686 1687 spec.results_ = new jasmine.NestedResults(); 1688 spec.results_.description = description; 1689 spec.matchersClass = null; 1690 }; 1691 1692 jasmine.Spec.prototype.getFullName = function() { 1693 return this.suite.getFullName() + ' ' + this.description + '.'; 1694 }; 1695 1696 1697 jasmine.Spec.prototype.results = function() { 1698 return this.results_; 1699 }; 1700 1701 jasmine.Spec.prototype.log = function(message) { 1702 return this.results_.log(message); 1703 }; 1704 1705 jasmine.Spec.prototype.runs = function (func) { 1706 var block = new jasmine.Block(this.env, func, this); 1707 this.addToQueue(block); 1708 return this; 1709 }; 1710 1711 jasmine.Spec.prototype.addToQueue = function (block) { 1712 if (this.queue.isRunning()) { 1713 this.queue.insertNext(block); 1714 } else { 1715 this.queue.add(block); 1716 } 1717 }; 1718 1719 jasmine.Spec.prototype.expect = function(actual) { 1720 return new (this.getMatchersClass_())(this.env, actual, this.results_); 1721 }; 1722 1723 jasmine.Spec.prototype.waits = function(timeout) { 1724 var waitsFunc = new jasmine.WaitsBlock(this.env, timeout, this); 1725 this.addToQueue(waitsFunc); 1726 return this; 1727 }; 1728 1729 jasmine.Spec.prototype.waitsFor = function(timeout, latchFunction, timeoutMessage) { 1730 var waitsForFunc = new jasmine.WaitsForBlock(this.env, timeout, latchFunction, timeoutMessage, this); 1731 this.addToQueue(waitsForFunc); 1732 return this; 1733 }; 1734 1735 jasmine.Spec.prototype.fail = function (e) { 1736 this.results_.addResult(new jasmine.ExpectationResult(false, e ? jasmine.util.formatException(e) : null, null)); 1737 }; 1738 1739 jasmine.Spec.prototype.getMatchersClass_ = function() { 1740 return this.matchersClass || jasmine.Matchers; 1741 }; 1742 1743 jasmine.Spec.prototype.addMatchers = function(matchersPrototype) { 1744 var parent = this.getMatchersClass_(); 1745 var newMatchersClass = function() { 1746 parent.apply(this, arguments); 1747 }; 1748 jasmine.util.inherit(newMatchersClass, parent); 1749 for (var method in matchersPrototype) { 1750 newMatchersClass.prototype[method] = matchersPrototype[method]; 1751 } 1752 this.matchersClass = newMatchersClass; 1753 }; 1754 1755 jasmine.Spec.prototype.finishCallback = function() { 1756 this.env.reporter.reportSpecResults(this); 1757 }; 1758 1759 jasmine.Spec.prototype.finish = function(onComplete) { 1760 this.removeAllSpies(); 1761 this.finishCallback(); 1762 if (onComplete) { 1763 onComplete(); 1764 } 1765 }; 1766 1767 jasmine.Spec.prototype.after = function(doAfter, test) { 1768 1769 if (this.queue.isRunning()) { 1770 this.queue.add(new jasmine.Block(this.env, doAfter, this)); 1771 } else { 1772 this.afterCallbacks.unshift(doAfter); 1773 } 1774 }; 1775 1776 jasmine.Spec.prototype.execute = function(onComplete) { 1777 var spec = this; 1778 if (!spec.env.specFilter(spec)) { 1779 spec.results_.skipped = true; 1780 spec.finish(onComplete); 1781 return; 1782 } 1783 this.env.reporter.log('>> Jasmine Running ' + this.suite.description + ' ' + this.description + '...'); 1784 1785 spec.env.currentSpec = spec; 1786 1787 spec.addBeforesAndAftersToQueue(); 1788 1789 spec.queue.start(function () { 1790 spec.finish(onComplete); 1791 }); 1792 }; 1793 1794 jasmine.Spec.prototype.addBeforesAndAftersToQueue = function() { 1795 var runner = this.env.currentRunner(); 1796 for (var suite = this.suite; suite; suite = suite.parentSuite) { 1797 for (var i = 0; i < suite.before_.length; i++) { 1798 this.queue.addBefore(new jasmine.Block(this.env, suite.before_[i], this)); 1799 } 1800 } 1801 for (var i = 0; i < runner.before_.length; i++) { 1802 this.queue.addBefore(new jasmine.Block(this.env, runner.before_[i], this)); 1803 } 1804 for (i = 0; i < this.afterCallbacks.length; i++) { 1805 this.queue.add(new jasmine.Block(this.env, this.afterCallbacks[i], this)); 1806 } 1807 for (suite = this.suite; suite; suite = suite.parentSuite) { 1808 for (var i = 0; i < suite.after_.length; i++) { 1809 this.queue.add(new jasmine.Block(this.env, suite.after_[i], this)); 1810 } 1811 } 1812 for (var i = 0; i < runner.after_.length; i++) { 1813 this.queue.add(new jasmine.Block(this.env, runner.after_[i], this)); 1814 } 1815 }; 1816 1817 jasmine.Spec.prototype.explodes = function() { 1818 throw 'explodes function should not have been called'; 1819 }; 1820 1821 jasmine.Spec.prototype.spyOn = function(obj, methodName, ignoreMethodDoesntExist) { 1822 if (obj == undefined) { 1823 throw "spyOn could not find an object to spy upon for " + methodName + "()"; 1824 } 1825 1826 if (!ignoreMethodDoesntExist && obj[methodName] === undefined) { 1827 throw methodName + '() method does not exist'; 1828 } 1829 1830 if (!ignoreMethodDoesntExist && obj[methodName] && obj[methodName].isSpy) { 1831 throw new Error(methodName + ' has already been spied upon'); 1832 } 1833 1834 var spyObj = jasmine.createSpy(methodName); 1835 1836 this.spies_.push(spyObj); 1837 spyObj.baseObj = obj; 1838 spyObj.methodName = methodName; 1839 spyObj.originalValue = obj[methodName]; 1840 1841 obj[methodName] = spyObj; 1842 1843 return spyObj; 1844 }; 1845 1846 jasmine.Spec.prototype.removeAllSpies = function() { 1847 for (var i = 0; i < this.spies_.length; i++) { 1848 var spy = this.spies_[i]; 1849 spy.baseObj[spy.methodName] = spy.originalValue; 1850 } 1851 this.spies_ = []; 1852 }; 1853 1854 /** 1855 * Internal representation of a Jasmine suite. 1856 * 1857 * @constructor 1858 * @param {jasmine.Env} env 1859 * @param {String} description 1860 * @param {Function} specDefinitions 1861 * @param {jasmine.Suite} parentSuite 1862 */ 1863 jasmine.Suite = function(env, description, specDefinitions, parentSuite) { 1864 var self = this; 1865 self.id = env.nextSuiteId ? env.nextSuiteId() : null; 1866 self.description = description; 1867 self.queue = new jasmine.Queue(env); 1868 self.parentSuite = parentSuite; 1869 self.env = env; 1870 self.before_ = []; 1871 self.after_ = []; 1872 self.specs_ = []; 1873 }; 1874 1875 jasmine.Suite.prototype.getFullName = function() { 1876 var fullName = this.description; 1877 for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) { 1878 fullName = parentSuite.description + ' ' + fullName; 1879 } 1880 return fullName; 1881 }; 1882 1883 jasmine.Suite.prototype.finish = function(onComplete) { 1884 this.env.reporter.reportSuiteResults(this); 1885 this.finished = true; 1886 if (typeof(onComplete) == 'function') { 1887 onComplete(); 1888 } 1889 }; 1890 1891 jasmine.Suite.prototype.beforeEach = function(beforeEachFunction) { 1892 beforeEachFunction.typeName = 'beforeEach'; 1893 this.before_.push(beforeEachFunction); 1894 }; 1895 1896 jasmine.Suite.prototype.afterEach = function(afterEachFunction) { 1897 afterEachFunction.typeName = 'afterEach'; 1898 this.after_.push(afterEachFunction); 1899 }; 1900 1901 jasmine.Suite.prototype.results = function() { 1902 return this.queue.results(); 1903 }; 1904 1905 jasmine.Suite.prototype.add = function(block) { 1906 if (block instanceof jasmine.Suite) { 1907 this.env.currentRunner().addSuite(block); 1908 } else { 1909 this.specs_.push(block); 1910 } 1911 this.queue.add(block); 1912 }; 1913 1914 jasmine.Suite.prototype.specs = function() { 1915 return this.specs_; 1916 }; 1917 1918 jasmine.Suite.prototype.execute = function(onComplete) { 1919 var self = this; 1920 this.queue.start(function () { 1921 self.finish(onComplete); 1922 }); 1923 }; 1924 jasmine.WaitsBlock = function(env, timeout, spec) { 1925 this.timeout = timeout; 1926 jasmine.Block.call(this, env, null, spec); 1927 }; 1928 1929 jasmine.util.inherit(jasmine.WaitsBlock, jasmine.Block); 1930 1931 jasmine.WaitsBlock.prototype.execute = function (onComplete) { 1932 this.env.reporter.log('>> Jasmine waiting for ' + this.timeout + ' ms...'); 1933 this.env.setTimeout(function () { 1934 onComplete(); 1935 }, this.timeout); 1936 }; 1937 jasmine.WaitsForBlock = function(env, timeout, latchFunction, message, spec) { 1938 this.timeout = timeout; 1939 this.latchFunction = latchFunction; 1940 this.message = message; 1941 this.totalTimeSpentWaitingForLatch = 0; 1942 jasmine.Block.call(this, env, null, spec); 1943 }; 1944 1945 jasmine.util.inherit(jasmine.WaitsForBlock, jasmine.Block); 1946 1947 jasmine.WaitsForBlock.TIMEOUT_INCREMENT = 100; 1948 1949 jasmine.WaitsForBlock.prototype.execute = function (onComplete) { 1950 var self = this; 1951 self.env.reporter.log('>> Jasmine waiting for ' + (self.message || 'something to happen')); 1952 var latchFunctionResult; 1953 try { 1954 latchFunctionResult = self.latchFunction.apply(self.spec); 1955 } catch (e) { 1956 self.spec.fail(e); 1957 onComplete(); 1958 return; 1959 } 1960 1961 if (latchFunctionResult) { 1962 onComplete(); 1963 } else if (self.totalTimeSpentWaitingForLatch >= self.timeout) { 1964 var message = 'timed out after ' + self.timeout + ' msec waiting for ' + (self.message || 'something to happen'); 1965 self.spec.fail({ 1966 name: 'timeout', 1967 message: message 1968 }); 1969 self.spec._next(); 1970 } else { 1971 self.totalTimeSpentWaitingForLatch += jasmine.WaitsForBlock.TIMEOUT_INCREMENT; 1972 self.env.setTimeout(function () { self.execute(onComplete); }, jasmine.WaitsForBlock.TIMEOUT_INCREMENT); 1973 } 1974 }; 1975 // Mock setTimeout, clearTimeout 1976 // Contributed by Pivotal Computer Systems, www.pivotalsf.com 1977 1978 jasmine.FakeTimer = function() { 1979 this.reset(); 1980 1981 var self = this; 1982 self.setTimeout = function(funcToCall, millis) { 1983 self.timeoutsMade++; 1984 self.scheduleFunction(self.timeoutsMade, funcToCall, millis, false); 1985 return self.timeoutsMade; 1986 }; 1987 1988 self.setInterval = function(funcToCall, millis) { 1989 self.timeoutsMade++; 1990 self.scheduleFunction(self.timeoutsMade, funcToCall, millis, true); 1991 return self.timeoutsMade; 1992 }; 1993 1994 self.clearTimeout = function(timeoutKey) { 1995 self.scheduledFunctions[timeoutKey] = undefined; 1996 }; 1997 1998 self.clearInterval = function(timeoutKey) { 1999 self.scheduledFunctions[timeoutKey] = undefined; 2000 }; 2001 2002 }; 2003 2004 jasmine.FakeTimer.prototype.reset = function() { 2005 this.timeoutsMade = 0; 2006 this.scheduledFunctions = {}; 2007 this.nowMillis = 0; 2008 }; 2009 2010 jasmine.FakeTimer.prototype.tick = function(millis) { 2011 var oldMillis = this.nowMillis; 2012 var newMillis = oldMillis + millis; 2013 this.runFunctionsWithinRange(oldMillis, newMillis); 2014 this.nowMillis = newMillis; 2015 }; 2016 2017 jasmine.FakeTimer.prototype.runFunctionsWithinRange = function(oldMillis, nowMillis) { 2018 var scheduledFunc; 2019 var funcsToRun = []; 2020 for (var timeoutKey in this.scheduledFunctions) { 2021 scheduledFunc = this.scheduledFunctions[timeoutKey]; 2022 if (scheduledFunc != undefined && 2023 scheduledFunc.runAtMillis >= oldMillis && 2024 scheduledFunc.runAtMillis <= nowMillis) { 2025 funcsToRun.push(scheduledFunc); 2026 this.scheduledFunctions[timeoutKey] = undefined; 2027 } 2028 } 2029 2030 if (funcsToRun.length > 0) { 2031 funcsToRun.sort(function(a, b) { 2032 return a.runAtMillis - b.runAtMillis; 2033 }); 2034 for (var i = 0; i < funcsToRun.length; ++i) { 2035 try { 2036 var funcToRun = funcsToRun[i]; 2037 this.nowMillis = funcToRun.runAtMillis; 2038 funcToRun.funcToCall(); 2039 if (funcToRun.recurring) { 2040 this.scheduleFunction(funcToRun.timeoutKey, 2041 funcToRun.funcToCall, 2042 funcToRun.millis, 2043 true); 2044 } 2045 } catch(e) { 2046 } 2047 } 2048 this.runFunctionsWithinRange(oldMillis, nowMillis); 2049 } 2050 }; 2051 2052 jasmine.FakeTimer.prototype.scheduleFunction = function(timeoutKey, funcToCall, millis, recurring) { 2053 this.scheduledFunctions[timeoutKey] = { 2054 runAtMillis: this.nowMillis + millis, 2055 funcToCall: funcToCall, 2056 recurring: recurring, 2057 timeoutKey: timeoutKey, 2058 millis: millis 2059 }; 2060 }; 2061 2062 2063 jasmine.Clock = { 2064 defaultFakeTimer: new jasmine.FakeTimer(), 2065 2066 reset: function() { 2067 jasmine.Clock.assertInstalled(); 2068 jasmine.Clock.defaultFakeTimer.reset(); 2069 }, 2070 2071 tick: function(millis) { 2072 jasmine.Clock.assertInstalled(); 2073 jasmine.Clock.defaultFakeTimer.tick(millis); 2074 }, 2075 2076 runFunctionsWithinRange: function(oldMillis, nowMillis) { 2077 jasmine.Clock.defaultFakeTimer.runFunctionsWithinRange(oldMillis, nowMillis); 2078 }, 2079 2080 scheduleFunction: function(timeoutKey, funcToCall, millis, recurring) { 2081 jasmine.Clock.defaultFakeTimer.scheduleFunction(timeoutKey, funcToCall, millis, recurring); 2082 }, 2083 2084 useMock: function() { 2085 var spec = jasmine.getEnv().currentSpec; 2086 spec.after(jasmine.Clock.uninstallMock); 2087 2088 jasmine.Clock.installMock(); 2089 }, 2090 2091 installMock: function() { 2092 jasmine.Clock.installed = jasmine.Clock.defaultFakeTimer; 2093 }, 2094 2095 uninstallMock: function() { 2096 jasmine.Clock.assertInstalled(); 2097 jasmine.Clock.installed = jasmine.Clock.real; 2098 }, 2099 2100 real: { 2101 setTimeout: window.setTimeout, 2102 clearTimeout: window.clearTimeout, 2103 setInterval: window.setInterval, 2104 clearInterval: window.clearInterval 2105 }, 2106 2107 assertInstalled: function() { 2108 if (jasmine.Clock.installed != jasmine.Clock.defaultFakeTimer) { 2109 throw new Error("Mock clock is not installed, use jasmine.Clock.useMock()"); 2110 } 2111 }, 2112 2113 installed: null 2114 }; 2115 jasmine.Clock.installed = jasmine.Clock.real; 2116 2117 //else for IE support 2118 window.setTimeout = function(funcToCall, millis) { 2119 if (jasmine.Clock.installed.setTimeout.apply) { 2120 return jasmine.Clock.installed.setTimeout.apply(this, arguments); 2121 } else { 2122 return jasmine.Clock.installed.setTimeout(funcToCall, millis); 2123 } 2124 }; 2125 2126 window.setInterval = function(funcToCall, millis) { 2127 if (jasmine.Clock.installed.setInterval.apply) { 2128 return jasmine.Clock.installed.setInterval.apply(this, arguments); 2129 } else { 2130 return jasmine.Clock.installed.setInterval(funcToCall, millis); 2131 } 2132 }; 2133 2134 window.clearTimeout = function(timeoutKey) { 2135 if (jasmine.Clock.installed.clearTimeout.apply) { 2136 return jasmine.Clock.installed.clearTimeout.apply(this, arguments); 2137 } else { 2138 return jasmine.Clock.installed.clearTimeout(timeoutKey); 2139 } 2140 }; 2141 2142 window.clearInterval = function(timeoutKey) { 2143 if (jasmine.Clock.installed.clearTimeout.apply) { 2144 return jasmine.Clock.installed.clearInterval.apply(this, arguments); 2145 } else { 2146 return jasmine.Clock.installed.clearInterval(timeoutKey); 2147 } 2148 }; 2149 2150