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