1 /** 2 * Top level namespace for Jasmine, a lightweight JavaScript BDD/spec/testing framework. 3 * 4 * @namespace 5 */ 6 var jasmine = {}; 7 8 /** 9 * @private 10 */ 11 jasmine.unimplementedMethod_ = function() { 12 throw new Error("unimplemented method"); 13 }; 14 15 /** 16 * Use <code>jasmine.undefined</code> instead of <code>undefined</code>, since <code>undefined</code> is just 17 * a plain old variable and may be redefined by somebody else. 18 * 19 * @private 20 */ 21 jasmine.undefined = jasmine.___undefined___; 22 23 /** 24 * Default interval for event loop yields. Small values here may result in slow test running. Zero means no updates until all tests have completed. 25 * 26 */ 27 jasmine.DEFAULT_UPDATE_INTERVAL = 250; 28 29 jasmine.getGlobal = function() { 30 function getGlobal() { 31 return this; 32 } 33 34 return getGlobal(); 35 }; 36 37 /** 38 * Allows for bound functions to be compared. Internal use only. 39 * 40 * @ignore 41 * @private 42 * @param base {Object} bound 'this' for the function 43 * @param name {Function} function to find 44 */ 45 jasmine.bindOriginal_ = function(base, name) { 46 var original = base[name]; 47 if (original.apply) { 48 return function() { 49 return original.apply(base, arguments); 50 }; 51 } else { 52 // IE support 53 return window[name]; 54 } 55 }; 56 57 jasmine.setTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'setTimeout'); 58 jasmine.clearTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearTimeout'); 59 jasmine.setInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'setInterval'); 60 jasmine.clearInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearInterval'); 61 62 jasmine.MessageResult = function(values) { 63 this.type = 'log'; 64 this.values = values; 65 this.trace = new Error(); // todo: test better 66 }; 67 68 jasmine.MessageResult.prototype.toString = function() { 69 var text = ""; 70 for(var i = 0; i < this.values.length; i++) { 71 if (i > 0) text += " "; 72 if (jasmine.isString_(this.values[i])) { 73 text += this.values[i]; 74 } else { 75 text += jasmine.pp(this.values[i]); 76 } 77 } 78 return text; 79 }; 80 81 jasmine.ExpectationResult = function(params) { 82 this.type = 'expect'; 83 this.matcherName = params.matcherName; 84 this.passed_ = params.passed; 85 this.expected = params.expected; 86 this.actual = params.actual; 87 88 this.message = this.passed_ ? 'Passed.' : params.message; 89 this.trace = this.passed_ ? '' : new Error(this.message); 90 }; 91 92 jasmine.ExpectationResult.prototype.toString = function () { 93 return this.message; 94 }; 95 96 jasmine.ExpectationResult.prototype.passed = function () { 97 return this.passed_; 98 }; 99 100 /** 101 * Getter for the Jasmine environment. Ensures one gets created 102 */ 103 jasmine.getEnv = function() { 104 return jasmine.currentEnv_ = jasmine.currentEnv_ || new jasmine.Env(); 105 }; 106 107 /** 108 * @ignore 109 * @private 110 * @param value 111 * @returns {Boolean} 112 */ 113 jasmine.isArray_ = function(value) { 114 return jasmine.isA_("Array", value); 115 }; 116 117 /** 118 * @ignore 119 * @private 120 * @param value 121 * @returns {Boolean} 122 */ 123 jasmine.isString_ = function(value) { 124 return jasmine.isA_("String", value); 125 }; 126 127 /** 128 * @ignore 129 * @private 130 * @param value 131 * @returns {Boolean} 132 */ 133 jasmine.isNumber_ = function(value) { 134 return jasmine.isA_("Number", value); 135 }; 136 137 /** 138 * @ignore 139 * @private 140 * @param {String} typeName 141 * @param value 142 * @returns {Boolean} 143 */ 144 jasmine.isA_ = function(typeName, value) { 145 return Object.prototype.toString.apply(value) === '[object ' + typeName + ']'; 146 }; 147 148 /** 149 * Pretty printer for expecations. Takes any object and turns it into a human-readable string. 150 * 151 * @param value {Object} an object to be outputted 152 * @returns {String} 153 */ 154 jasmine.pp = function(value) { 155 var stringPrettyPrinter = new jasmine.StringPrettyPrinter(); 156 stringPrettyPrinter.format(value); 157 return stringPrettyPrinter.string; 158 }; 159 160 /** 161 * Returns true if the object is a DOM Node. 162 * 163 * @param {Object} obj object to check 164 * @returns {Boolean} 165 */ 166 jasmine.isDomNode = function(obj) { 167 return obj['nodeType'] > 0; 168 }; 169 170 /** 171 * Returns a matchable 'generic' object of the class type. For use in expecations of type when values don't matter. 172 * 173 * @example 174 * // don't care about which function is passed in, as long as it's a function 175 * expect(mySpy).wasCalledWith(jasmine.any(Function)); 176 * 177 * @param {Class} clazz 178 * @returns matchable object of the type clazz 179 */ 180 jasmine.any = function(clazz) { 181 return new jasmine.Matchers.Any(clazz); 182 }; 183 184 /** 185 * Jasmine Spies are test doubles that can act as stubs, spies, fakes or when used in an expecation, mocks. 186 * 187 * Spies should be created in test setup, before expectations. They can then be checked, using the standard Jasmine 188 * expectation syntax. Spies can be checked if they were called or not and what the calling params were. 189 * 190 * A Spy has the following mehtod: wasCalled, callCount, mostRecentCall, and argsForCall (see docs) 191 * Spies are torn down at the end of every spec. 192 * 193 * Note: Do <b>not</b> call new jasmine.Spy() directly - a spy must be created using spyOn, jasmine.createSpy or jasmine.createSpyObj. 194 * 195 * @example 196 * // a stub 197 * var myStub = jasmine.createSpy('myStub'); // can be used anywhere 198 * 199 * // spy example 200 * var foo = { 201 * not: function(bool) { return !bool; } 202 * } 203 * 204 * // actual foo.not will not be called, execution stops 205 * spyOn(foo, 'not'); 206 207 // foo.not spied upon, execution will continue to implementation 208 * spyOn(foo, 'not').andCallThrough(); 209 * 210 * // fake example 211 * var foo = { 212 * not: function(bool) { return !bool; } 213 * } 214 * 215 * // foo.not(val) will return val 216 * spyOn(foo, 'not').andCallFake(function(value) {return value;}); 217 * 218 * // mock example 219 * foo.not(7 == 7); 220 * expect(foo.not).wasCalled(); 221 * expect(foo.not).wasCalledWith(true); 222 * 223 * @constructor 224 * @see spyOn, jasmine.createSpy, jasmine.createSpyObj 225 * @param {String} name 226 */ 227 jasmine.Spy = function(name) { 228 /** 229 * The name of the spy, if provided. 230 */ 231 this.identity = name || 'unknown'; 232 /** 233 * Is this Object a spy? 234 */ 235 this.isSpy = true; 236 /** 237 * The actual function this spy stubs. 238 */ 239 this.plan = function() { 240 }; 241 /** 242 * Tracking of the most recent call to the spy. 243 * @example 244 * var mySpy = jasmine.createSpy('foo'); 245 * mySpy(1, 2); 246 * mySpy.mostRecentCall.args = [1, 2]; 247 */ 248 this.mostRecentCall = {}; 249 250 /** 251 * Holds arguments for each call to the spy, indexed by call count 252 * @example 253 * var mySpy = jasmine.createSpy('foo'); 254 * mySpy(1, 2); 255 * mySpy(7, 8); 256 * mySpy.mostRecentCall.args = [7, 8]; 257 * mySpy.argsForCall[0] = [1, 2]; 258 * mySpy.argsForCall[1] = [7, 8]; 259 */ 260 this.argsForCall = []; 261 this.calls = []; 262 }; 263 264 /** 265 * Tells a spy to call through to the actual implemenatation. 266 * 267 * @example 268 * var foo = { 269 * bar: function() { // do some stuff } 270 * } 271 * 272 * // defining a spy on an existing property: foo.bar 273 * spyOn(foo, 'bar').andCallThrough(); 274 */ 275 jasmine.Spy.prototype.andCallThrough = function() { 276 this.plan = this.originalValue; 277 return this; 278 }; 279 280 /** 281 * For setting the return value of a spy. 282 * 283 * @example 284 * // defining a spy from scratch: foo() returns 'baz' 285 * var foo = jasmine.createSpy('spy on foo').andReturn('baz'); 286 * 287 * // defining a spy on an existing property: foo.bar() returns 'baz' 288 * spyOn(foo, 'bar').andReturn('baz'); 289 * 290 * @param {Object} value 291 */ 292 jasmine.Spy.prototype.andReturn = function(value) { 293 this.plan = function() { 294 return value; 295 }; 296 return this; 297 }; 298 299 /** 300 * For throwing an exception when a spy is called. 301 * 302 * @example 303 * // defining a spy from scratch: foo() throws an exception w/ message 'ouch' 304 * var foo = jasmine.createSpy('spy on foo').andThrow('baz'); 305 * 306 * // defining a spy on an existing property: foo.bar() throws an exception w/ message 'ouch' 307 * spyOn(foo, 'bar').andThrow('baz'); 308 * 309 * @param {String} exceptionMsg 310 */ 311 jasmine.Spy.prototype.andThrow = function(exceptionMsg) { 312 this.plan = function() { 313 throw exceptionMsg; 314 }; 315 return this; 316 }; 317 318 /** 319 * Calls an alternate implementation when a spy is called. 320 * 321 * @example 322 * var baz = function() { 323 * // do some stuff, return something 324 * } 325 * // defining a spy from scratch: foo() calls the function baz 326 * var foo = jasmine.createSpy('spy on foo').andCall(baz); 327 * 328 * // defining a spy on an existing property: foo.bar() calls an anonymnous function 329 * spyOn(foo, 'bar').andCall(function() { return 'baz';} ); 330 * 331 * @param {Function} fakeFunc 332 */ 333 jasmine.Spy.prototype.andCallFake = function(fakeFunc) { 334 this.plan = fakeFunc; 335 return this; 336 }; 337 338 /** 339 * Resets all of a spy's the tracking variables so that it can be used again. 340 * 341 * @example 342 * spyOn(foo, 'bar'); 343 * 344 * foo.bar(); 345 * 346 * expect(foo.bar.callCount).toEqual(1); 347 * 348 * foo.bar.reset(); 349 * 350 * expect(foo.bar.callCount).toEqual(0); 351 */ 352 jasmine.Spy.prototype.reset = function() { 353 this.wasCalled = false; 354 this.callCount = 0; 355 this.argsForCall = []; 356 this.calls = []; 357 this.mostRecentCall = {}; 358 }; 359 360 jasmine.createSpy = function(name) { 361 362 var spyObj = function() { 363 spyObj.wasCalled = true; 364 spyObj.callCount++; 365 var args = jasmine.util.argsToArray(arguments); 366 spyObj.mostRecentCall.object = this; 367 spyObj.mostRecentCall.args = args; 368 spyObj.argsForCall.push(args); 369 spyObj.calls.push({object: this, args: args}); 370 return spyObj.plan.apply(this, arguments); 371 }; 372 373 var spy = new jasmine.Spy(name); 374 375 for (var prop in spy) { 376 spyObj[prop] = spy[prop]; 377 } 378 379 spyObj.reset(); 380 381 return spyObj; 382 }; 383 384 /** 385 * Determines whether an object is a spy. 386 * 387 * @param {jasmine.Spy|Object} putativeSpy 388 * @returns {Boolean} 389 */ 390 jasmine.isSpy = function(putativeSpy) { 391 return putativeSpy && putativeSpy.isSpy; 392 }; 393 394 /** 395 * Creates a more complicated spy: an Object that has every property a function that is a spy. Used for stubbing something 396 * large in one call. 397 * 398 * @param {String} baseName name of spy class 399 * @param {Array} methodNames array of names of methods to make spies 400 */ 401 jasmine.createSpyObj = function(baseName, methodNames) { 402 if (!jasmine.isArray_(methodNames) || methodNames.length == 0) { 403 throw new Error('createSpyObj requires a non-empty array of method names to create spies for'); 404 } 405 var obj = {}; 406 for (var i = 0; i < methodNames.length; i++) { 407 obj[methodNames[i]] = jasmine.createSpy(baseName + '.' + methodNames[i]); 408 } 409 return obj; 410 }; 411 412 jasmine.log = function() { 413 var spec = jasmine.getEnv().currentSpec; 414 spec.log.apply(spec, arguments); 415 }; 416 417 /** 418 * Function that installs a spy on an existing object's method name. Used within a Spec to create a spy. 419 * 420 * @example 421 * // spy example 422 * var foo = { 423 * not: function(bool) { return !bool; } 424 * } 425 * spyOn(foo, 'not'); // actual foo.not will not be called, execution stops 426 * 427 * @see jasmine.createSpy 428 * @param obj 429 * @param methodName 430 * @returns a Jasmine spy that can be chained with all spy methods 431 */ 432 var spyOn = function(obj, methodName) { 433 return jasmine.getEnv().currentSpec.spyOn(obj, methodName); 434 }; 435 436 /** 437 * Creates a Jasmine spec that will be added to the current suite. 438 * 439 * // TODO: pending tests 440 * 441 * @example 442 * it('should be true', function() { 443 * expect(true).toEqual(true); 444 * }); 445 * 446 * @param {String} desc description of this specification 447 * @param {Function} func defines the preconditions and expectations of the spec 448 */ 449 var it = function(desc, func) { 450 return jasmine.getEnv().it(desc, func); 451 }; 452 453 /** 454 * Creates a <em>disabled</em> Jasmine spec. 455 * 456 * A convenience method that allows existing specs to be disabled temporarily during development. 457 * 458 * @param {String} desc description of this specification 459 * @param {Function} func defines the preconditions and expectations of the spec 460 */ 461 var xit = function(desc, func) { 462 return jasmine.getEnv().xit(desc, func); 463 }; 464 465 /** 466 * Starts a chain for a Jasmine expectation. 467 * 468 * It is passed an Object that is the actual value and should chain to one of the many 469 * jasmine.Matchers functions. 470 * 471 * @param {Object} actual Actual value to test against and expected value 472 */ 473 var expect = function(actual) { 474 return jasmine.getEnv().currentSpec.expect(actual); 475 }; 476 477 /** 478 * Defines part of a jasmine spec. Used in cominbination with waits or waitsFor in asynchrnous specs. 479 * 480 * @param {Function} func Function that defines part of a jasmine spec. 481 */ 482 var runs = function(func) { 483 jasmine.getEnv().currentSpec.runs(func); 484 }; 485 486 /** 487 * Waits for a timeout before moving to the next runs()-defined block. 488 * @param {Number} timeout 489 */ 490 var waits = function(timeout) { 491 jasmine.getEnv().currentSpec.waits(timeout); 492 }; 493 494 /** 495 * Waits for the latchFunction to return true before proceeding to the next runs()-defined block. 496 * 497 * @param {Number} timeout 498 * @param {Function} latchFunction 499 * @param {String} message 500 */ 501 var waitsFor = function(timeout, latchFunction, message) { 502 jasmine.getEnv().currentSpec.waitsFor(timeout, latchFunction, message); 503 }; 504 505 /** 506 * A function that is called before each spec in a suite. 507 * 508 * Used for spec setup, including validating assumptions. 509 * 510 * @param {Function} beforeEachFunction 511 */ 512 var beforeEach = function(beforeEachFunction) { 513 jasmine.getEnv().beforeEach(beforeEachFunction); 514 }; 515 516 /** 517 * A function that is called after each spec in a suite. 518 * 519 * Used for restoring any state that is hijacked during spec execution. 520 * 521 * @param {Function} afterEachFunction 522 */ 523 var afterEach = function(afterEachFunction) { 524 jasmine.getEnv().afterEach(afterEachFunction); 525 }; 526 527 /** 528 * Defines a suite of specifications. 529 * 530 * Stores the description and all defined specs in the Jasmine environment as one suite of specs. Variables declared 531 * are accessible by calls to beforeEach, it, and afterEach. Describe blocks can be nested, allowing for specialization 532 * of setup in some tests. 533 * 534 * @example 535 * // TODO: a simple suite 536 * 537 * // TODO: a simple suite with a nested describe block 538 * 539 * @param {String} description A string, usually the class under test. 540 * @param {Function} specDefinitions function that defines several specs. 541 */ 542 var describe = function(description, specDefinitions) { 543 return jasmine.getEnv().describe(description, specDefinitions); 544 }; 545 546 /** 547 * Disables a suite of specifications. Used to disable some suites in a file, or files, temporarily during development. 548 * 549 * @param {String} description A string, usually the class under test. 550 * @param {Function} specDefinitions function that defines several specs. 551 */ 552 var xdescribe = function(description, specDefinitions) { 553 return jasmine.getEnv().xdescribe(description, specDefinitions); 554 }; 555 556 557 // Provide the XMLHttpRequest class for IE 5.x-6.x: 558 jasmine.XmlHttpRequest = (typeof XMLHttpRequest == "undefined") ? function() { 559 try { 560 return new ActiveXObject("Msxml2.XMLHTTP.6.0"); 561 } catch(e) { 562 } 563 try { 564 return new ActiveXObject("Msxml2.XMLHTTP.3.0"); 565 } catch(e) { 566 } 567 try { 568 return new ActiveXObject("Msxml2.XMLHTTP"); 569 } catch(e) { 570 } 571 try { 572 return new ActiveXObject("Microsoft.XMLHTTP"); 573 } catch(e) { 574 } 575 throw new Error("This browser does not support XMLHttpRequest."); 576 } : XMLHttpRequest; 577