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 jasmine.getGlobal()[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).toHaveBeenCalledWith(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 fields: wasCalled, callCount, mostRecentCall, and argsForCall (see docs). 191 * 192 * Spies are torn down at the end of every spec. 193 * 194 * Note: Do <b>not</b> call new jasmine.Spy() directly - a spy must be created using spyOn, jasmine.createSpy or jasmine.createSpyObj. 195 * 196 * @example 197 * // a stub 198 * var myStub = jasmine.createSpy('myStub'); // can be used anywhere 199 * 200 * // spy example 201 * var foo = { 202 * not: function(bool) { return !bool; } 203 * } 204 * 205 * // actual foo.not will not be called, execution stops 206 * spyOn(foo, 'not'); 207 208 // foo.not spied upon, execution will continue to implementation 209 * spyOn(foo, 'not').andCallThrough(); 210 * 211 * // fake example 212 * var foo = { 213 * not: function(bool) { return !bool; } 214 * } 215 * 216 * // foo.not(val) will return val 217 * spyOn(foo, 'not').andCallFake(function(value) {return value;}); 218 * 219 * // mock example 220 * foo.not(7 == 7); 221 * expect(foo.not).toHaveBeenCalled(); 222 * expect(foo.not).toHaveBeenCalledWith(true); 223 * 224 * @constructor 225 * @see spyOn, jasmine.createSpy, jasmine.createSpyObj 226 * @param {String} name 227 */ 228 jasmine.Spy = function(name) { 229 /** 230 * The name of the spy, if provided. 231 */ 232 this.identity = name || 'unknown'; 233 /** 234 * Is this Object a spy? 235 */ 236 this.isSpy = true; 237 /** 238 * The actual function this spy stubs. 239 */ 240 this.plan = function() { 241 }; 242 /** 243 * Tracking of the most recent call to the spy. 244 * @example 245 * var mySpy = jasmine.createSpy('foo'); 246 * mySpy(1, 2); 247 * mySpy.mostRecentCall.args = [1, 2]; 248 */ 249 this.mostRecentCall = {}; 250 251 /** 252 * Holds arguments for each call to the spy, indexed by call count 253 * @example 254 * var mySpy = jasmine.createSpy('foo'); 255 * mySpy(1, 2); 256 * mySpy(7, 8); 257 * mySpy.mostRecentCall.args = [7, 8]; 258 * mySpy.argsForCall[0] = [1, 2]; 259 * mySpy.argsForCall[1] = [7, 8]; 260 */ 261 this.argsForCall = []; 262 this.calls = []; 263 }; 264 265 /** 266 * Tells a spy to call through to the actual implemenatation. 267 * 268 * @example 269 * var foo = { 270 * bar: function() { // do some stuff } 271 * } 272 * 273 * // defining a spy on an existing property: foo.bar 274 * spyOn(foo, 'bar').andCallThrough(); 275 */ 276 jasmine.Spy.prototype.andCallThrough = function() { 277 this.plan = this.originalValue; 278 return this; 279 }; 280 281 /** 282 * For setting the return value of a spy. 283 * 284 * @example 285 * // defining a spy from scratch: foo() returns 'baz' 286 * var foo = jasmine.createSpy('spy on foo').andReturn('baz'); 287 * 288 * // defining a spy on an existing property: foo.bar() returns 'baz' 289 * spyOn(foo, 'bar').andReturn('baz'); 290 * 291 * @param {Object} value 292 */ 293 jasmine.Spy.prototype.andReturn = function(value) { 294 this.plan = function() { 295 return value; 296 }; 297 return this; 298 }; 299 300 /** 301 * For throwing an exception when a spy is called. 302 * 303 * @example 304 * // defining a spy from scratch: foo() throws an exception w/ message 'ouch' 305 * var foo = jasmine.createSpy('spy on foo').andThrow('baz'); 306 * 307 * // defining a spy on an existing property: foo.bar() throws an exception w/ message 'ouch' 308 * spyOn(foo, 'bar').andThrow('baz'); 309 * 310 * @param {String} exceptionMsg 311 */ 312 jasmine.Spy.prototype.andThrow = function(exceptionMsg) { 313 this.plan = function() { 314 throw exceptionMsg; 315 }; 316 return this; 317 }; 318 319 /** 320 * Calls an alternate implementation when a spy is called. 321 * 322 * @example 323 * var baz = function() { 324 * // do some stuff, return something 325 * } 326 * // defining a spy from scratch: foo() calls the function baz 327 * var foo = jasmine.createSpy('spy on foo').andCall(baz); 328 * 329 * // defining a spy on an existing property: foo.bar() calls an anonymnous function 330 * spyOn(foo, 'bar').andCall(function() { return 'baz';} ); 331 * 332 * @param {Function} fakeFunc 333 */ 334 jasmine.Spy.prototype.andCallFake = function(fakeFunc) { 335 this.plan = fakeFunc; 336 return this; 337 }; 338 339 /** 340 * Resets all of a spy's the tracking variables so that it can be used again. 341 * 342 * @example 343 * spyOn(foo, 'bar'); 344 * 345 * foo.bar(); 346 * 347 * expect(foo.bar.callCount).toEqual(1); 348 * 349 * foo.bar.reset(); 350 * 351 * expect(foo.bar.callCount).toEqual(0); 352 */ 353 jasmine.Spy.prototype.reset = function() { 354 this.wasCalled = false; 355 this.callCount = 0; 356 this.argsForCall = []; 357 this.calls = []; 358 this.mostRecentCall = {}; 359 }; 360 361 jasmine.createSpy = function(name) { 362 363 var spyObj = function() { 364 spyObj.wasCalled = true; 365 spyObj.callCount++; 366 var args = jasmine.util.argsToArray(arguments); 367 spyObj.mostRecentCall.object = this; 368 spyObj.mostRecentCall.args = args; 369 spyObj.argsForCall.push(args); 370 spyObj.calls.push({object: this, args: args}); 371 return spyObj.plan.apply(this, arguments); 372 }; 373 374 var spy = new jasmine.Spy(name); 375 376 for (var prop in spy) { 377 spyObj[prop] = spy[prop]; 378 } 379 380 spyObj.reset(); 381 382 return spyObj; 383 }; 384 385 /** 386 * Determines whether an object is a spy. 387 * 388 * @param {jasmine.Spy|Object} putativeSpy 389 * @returns {Boolean} 390 */ 391 jasmine.isSpy = function(putativeSpy) { 392 return putativeSpy && putativeSpy.isSpy; 393 }; 394 395 /** 396 * Creates a more complicated spy: an Object that has every property a function that is a spy. Used for stubbing something 397 * large in one call. 398 * 399 * @param {String} baseName name of spy class 400 * @param {Array} methodNames array of names of methods to make spies 401 */ 402 jasmine.createSpyObj = function(baseName, methodNames) { 403 if (!jasmine.isArray_(methodNames) || methodNames.length == 0) { 404 throw new Error('createSpyObj requires a non-empty array of method names to create spies for'); 405 } 406 var obj = {}; 407 for (var i = 0; i < methodNames.length; i++) { 408 obj[methodNames[i]] = jasmine.createSpy(baseName + '.' + methodNames[i]); 409 } 410 return obj; 411 }; 412 413 /** 414 * All parameters are pretty-printed and concatenated together, then written to the current spec's output. 415 * 416 * Be careful not to leave calls to <code>jasmine.log</code> in production code. 417 */ 418 jasmine.log = function() { 419 var spec = jasmine.getEnv().currentSpec; 420 spec.log.apply(spec, arguments); 421 }; 422 423 /** 424 * Function that installs a spy on an existing object's method name. Used within a Spec to create a spy. 425 * 426 * @example 427 * // spy example 428 * var foo = { 429 * not: function(bool) { return !bool; } 430 * } 431 * spyOn(foo, 'not'); // actual foo.not will not be called, execution stops 432 * 433 * @see jasmine.createSpy 434 * @param obj 435 * @param methodName 436 * @returns a Jasmine spy that can be chained with all spy methods 437 */ 438 var spyOn = function(obj, methodName) { 439 return jasmine.getEnv().currentSpec.spyOn(obj, methodName); 440 }; 441 442 /** 443 * Creates a Jasmine spec that will be added to the current suite. 444 * 445 * // TODO: pending tests 446 * 447 * @example 448 * it('should be true', function() { 449 * expect(true).toEqual(true); 450 * }); 451 * 452 * @param {String} desc description of this specification 453 * @param {Function} func defines the preconditions and expectations of the spec 454 */ 455 var it = function(desc, func) { 456 return jasmine.getEnv().it(desc, func); 457 }; 458 459 /** 460 * Creates a <em>disabled</em> Jasmine spec. 461 * 462 * A convenience method that allows existing specs to be disabled temporarily during development. 463 * 464 * @param {String} desc description of this specification 465 * @param {Function} func defines the preconditions and expectations of the spec 466 */ 467 var xit = function(desc, func) { 468 return jasmine.getEnv().xit(desc, func); 469 }; 470 471 /** 472 * Starts a chain for a Jasmine expectation. 473 * 474 * It is passed an Object that is the actual value and should chain to one of the many 475 * jasmine.Matchers functions. 476 * 477 * @param {Object} actual Actual value to test against and expected value 478 */ 479 var expect = function(actual) { 480 return jasmine.getEnv().currentSpec.expect(actual); 481 }; 482 483 /** 484 * Defines part of a jasmine spec. Used in cominbination with waits or waitsFor in asynchrnous specs. 485 * 486 * @param {Function} func Function that defines part of a jasmine spec. 487 */ 488 var runs = function(func) { 489 jasmine.getEnv().currentSpec.runs(func); 490 }; 491 492 /** 493 * Waits for a timeout before moving to the next runs()-defined block. 494 * @param {Number} timeout 495 */ 496 var waits = function(timeout) { 497 jasmine.getEnv().currentSpec.waits(timeout); 498 }; 499 500 /** 501 * Waits for the latchFunction to return true before proceeding to the next runs()-defined block. 502 * 503 * @param {Number} timeout 504 * @param {Function} latchFunction 505 * @param {String} message 506 */ 507 var waitsFor = function(timeout, latchFunction, message) { 508 jasmine.getEnv().currentSpec.waitsFor(timeout, latchFunction, message); 509 }; 510 511 /** 512 * A function that is called before each spec in a suite. 513 * 514 * Used for spec setup, including validating assumptions. 515 * 516 * @param {Function} beforeEachFunction 517 */ 518 var beforeEach = function(beforeEachFunction) { 519 jasmine.getEnv().beforeEach(beforeEachFunction); 520 }; 521 522 /** 523 * A function that is called after each spec in a suite. 524 * 525 * Used for restoring any state that is hijacked during spec execution. 526 * 527 * @param {Function} afterEachFunction 528 */ 529 var afterEach = function(afterEachFunction) { 530 jasmine.getEnv().afterEach(afterEachFunction); 531 }; 532 533 /** 534 * Defines a suite of specifications. 535 * 536 * Stores the description and all defined specs in the Jasmine environment as one suite of specs. Variables declared 537 * are accessible by calls to beforeEach, it, and afterEach. Describe blocks can be nested, allowing for specialization 538 * of setup in some tests. 539 * 540 * @example 541 * // TODO: a simple suite 542 * 543 * // TODO: a simple suite with a nested describe block 544 * 545 * @param {String} description A string, usually the class under test. 546 * @param {Function} specDefinitions function that defines several specs. 547 */ 548 var describe = function(description, specDefinitions) { 549 return jasmine.getEnv().describe(description, specDefinitions); 550 }; 551 552 /** 553 * Disables a suite of specifications. Used to disable some suites in a file, or files, temporarily during development. 554 * 555 * @param {String} description A string, usually the class under test. 556 * @param {Function} specDefinitions function that defines several specs. 557 */ 558 var xdescribe = function(description, specDefinitions) { 559 return jasmine.getEnv().xdescribe(description, specDefinitions); 560 }; 561 562 563 // Provide the XMLHttpRequest class for IE 5.x-6.x: 564 jasmine.XmlHttpRequest = (typeof XMLHttpRequest == "undefined") ? function() { 565 try { 566 return new ActiveXObject("Msxml2.XMLHTTP.6.0"); 567 } catch(e) { 568 } 569 try { 570 return new ActiveXObject("Msxml2.XMLHTTP.3.0"); 571 } catch(e) { 572 } 573 try { 574 return new ActiveXObject("Msxml2.XMLHTTP"); 575 } catch(e) { 576 } 577 try { 578 return new ActiveXObject("Microsoft.XMLHTTP"); 579 } catch(e) { 580 } 581 throw new Error("This browser does not support XMLHttpRequest."); 582 } : XMLHttpRequest; 583