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