/* * Jasmine internal classes & objects */ var Jasmine = {}; Jasmine.util = {}; /** @deprecated Use Jasmine.util instead */ Jasmine.Util = Jasmine.util; Jasmine.util.inherit = function(childClass, parentClass) { var subclass = function() { }; subclass.prototype = parentClass.prototype; childClass.prototype = new subclass; }; /* * Holds results; allows for the results array to hold another Jasmine.NestedResults */ Jasmine.NestedResults = function() { this.totalCount = 0; this.passedCount = 0; this.failedCount = 0; this.results = []; }; Jasmine.NestedResults.prototype.rollupCounts = function(result) { this.totalCount += result.totalCount; this.passedCount += result.passedCount; this.failedCount += result.failedCount; }; Jasmine.NestedResults.prototype.push = function(result) { if (result.results) { this.rollupCounts(result); } else { this.totalCount++; if (result.passed) { this.passedCount++; } else { this.failedCount++; } } this.results.push(result); }; Jasmine.NestedResults.prototype.passed = function() { return this.passedCount === this.totalCount; }; /* * base for Runner & Suite: allows for a queue of functions to get executed, allowing for * any one action to complete, including asynchronous calls, before going to the next * action. * **/ Jasmine.ActionCollection = function() { this.actions = []; this.index = 0; this.finished = false; this.results = new Jasmine.NestedResults(); }; Jasmine.ActionCollection.prototype.finish = function() { if (this.finishCallback) { this.finishCallback(); } this.finished = true; }; Jasmine.ActionCollection.prototype.report = function(result) { this.results.push(result); }; Jasmine.ActionCollection.prototype.execute = function() { if (this.actions.length > 0) { this.next(); } }; Jasmine.ActionCollection.prototype.getCurrentAction = function() { return this.actions[this.index]; }; Jasmine.ActionCollection.prototype.next = function() { if (this.index >= this.actions.length) { this.finish(); return; } var currentAction = this.getCurrentAction(); currentAction.execute(this); if (currentAction.afterCallbacks) { for (var i = 0; i < currentAction.afterCallbacks.length; i++) { try { currentAction.afterCallbacks[i](); } catch (e) { alert(e); } } } this.waitForDone(currentAction); }; Jasmine.ActionCollection.prototype.waitForDone = function(action) { var self = this; var afterExecute = function() { self.report(action.results); self.index++; self.next(); }; if (action.finished) { afterExecute(); return; } var id = setInterval(function() { if (action.finished) { clearInterval(id); afterExecute(); } }, 150); }; Jasmine.safeExecuteBeforeOrAfter = function(spec, func) { try { func.apply(spec); } catch (e) { var fail = {passed: false, message: func.typeName + '() fail: ' + Jasmine.util.formatException(e)}; spec.results.push(fail); } }; /* * QueuedFunction is how ActionCollections' actions are implemented */ Jasmine.QueuedFunction = function(func, timeout, latchFunction, spec) { this.func = func; this.timeout = timeout; this.latchFunction = latchFunction; this.spec = spec; this.totalTimeSpentWaitingForLatch = 0; this.latchTimeoutIncrement = 100; }; Jasmine.QueuedFunction.prototype.next = function() { this.spec.finish(); // default value is to be done after one function }; Jasmine.QueuedFunction.prototype.safeExecute = function() { if (console) { console.log('>> Jasmine Running ' + this.spec.suite.description + ' ' + this.spec.description + '...'); } try { this.func.apply(this.spec); } catch (e) { this.fail(e); } }; Jasmine.QueuedFunction.prototype.execute = function() { var self = this; var executeNow = function() { self.safeExecute(); self.next(); }; var executeLater = function() { setTimeout(executeNow, self.timeout); }; var executeNowOrLater = function() { var latchFunctionResult; try { latchFunctionResult = self.latchFunction.apply(self.spec); } catch (e) { self.fail(e); self.next(); return; } if (latchFunctionResult) { executeNow(); } else if (self.totalTimeSpentWaitingForLatch >= self.timeout) { var message = 'timed out after ' + self.timeout + ' msec waiting for ' + (self.latchFunction.description || 'something to happen'); self.fail({ name: 'timeout', message: message }); self.next(); } else { self.totalTimeSpentWaitingForLatch += self.latchTimeoutIncrement; setTimeout(executeNowOrLater, self.latchTimeoutIncrement); } }; if (this.latchFunction !== undefined) { executeNowOrLater(); } else if (this.timeout > 0) { executeLater(); } else { executeNow(); } }; Jasmine.QueuedFunction.prototype.fail = function(e) { this.spec.results.push({passed:false, message: Jasmine.util.formatException(e)}); }; /****************************************************************************** * Jasmine ******************************************************************************/ Jasmine.Env = function() { this.currentSpec = null; this.currentSuite = null; this.currentRunner = null; }; Jasmine.Env.prototype.execute = function() { this.currentRunner.execute(); }; Jasmine.currentEnv_ = new Jasmine.Env(); /** @deprecated use Jasmine.getEnv() instead */ var jasmine = Jasmine.currentEnv_; Jasmine.getEnv = function() { return Jasmine.currentEnv_; }; Jasmine.isArray_ = function(value) { return value && typeof value === 'object' && typeof value.length === 'number' && typeof value.splice === 'function' && !(value.propertyIsEnumerable('length')); }; Jasmine.arrayToString_ = function(array) { var formatted_value = ''; for (var i = 0; i < array.length; i++) { if (i > 0) { formatted_value += ', '; } ; formatted_value += Jasmine.pp(array[i]); } return '[ ' + formatted_value + ' ]'; }; Jasmine.objectToString_ = function(obj) { var formatted_value = ''; var first = true; for (var property in obj) { if (property == '__Jasmine_pp_has_traversed__') continue; if (first) { first = false; } else { formatted_value += ', '; } formatted_value += property; formatted_value += ' : '; formatted_value += Jasmine.pp(obj[property]); } return '{ ' + formatted_value + ' }'; }; Jasmine.ppNestLevel_ = 0; Jasmine.pp = function(value) { if (Jasmine.ppNestLevel_ > 40) { // return '(Jasmine.pp nested too deeply!)'; throw new Error('Jasmine.pp nested too deeply!'); } Jasmine.ppNestLevel_++; try { return Jasmine.pp_(value); } finally { Jasmine.ppNestLevel_--; } }; Jasmine.pp_ = function(value) { if (value === undefined) { return 'undefined'; } if (value === null) { return 'null'; } if (value.navigator && value.frames && value.setTimeout) { return ''; } if (value instanceof Jasmine.Any) return value.toString(); if (typeof value === 'string') { return "'" + Jasmine.util.htmlEscape(value) + "'"; } if (typeof value === 'function') { return 'Function'; } if (typeof value.nodeType === 'number') { return 'HTMLNode'; } if (value.__Jasmine_pp_has_traversed__) { return ''; } if (Jasmine.isArray_(value) || typeof value == 'object') { value.__Jasmine_pp_has_traversed__ = true; var stringified = Jasmine.isArray_(value) ? Jasmine.arrayToString_(value) : Jasmine.objectToString_(value); delete value.__Jasmine_pp_has_traversed__; return stringified; } return Jasmine.util.htmlEscape(value.toString()); }; /* * Jasmine.Matchers methods; add your own by extending Jasmine.Matchers.prototype - don't forget to write a test * */ Jasmine.Matchers = function(actual, results) { this.actual = actual; this.passing_message = 'Passed.'; this.results = results || new Jasmine.NestedResults(); }; Jasmine.Matchers.prototype.report = function(result, failing_message) { this.results.push({ passed: result, message: result ? this.passing_message : failing_message }); return result; }; Jasmine.isDomNode = function(obj) { return obj['nodeType'] > 0; }; Jasmine.Any = function(expectedClass) { this.expectedClass = expectedClass; }; Jasmine.Any.prototype.matches = function(other) { if (this.expectedClass == String) { return typeof other == 'string' || other instanceof String; } if (this.expectedClass == Number) { return typeof other == 'number' || other instanceof Number; } return other instanceof this.expectedClass; }; Jasmine.Any.prototype.toString = function() { return ''; }; Jasmine.any = function(clazz) { return new Jasmine.Any(clazz); }; Jasmine.Matchers.prototype.toEqual = function(expected) { var mismatchKeys = []; var mismatchValues = []; var hasKey = function(obj, keyName) { return obj!=null && obj[keyName] !== undefined; }; var equal = function(a, b) { if (a == undefined || a == null) { return (a == undefined && b == undefined); } if (Jasmine.isDomNode(a) && Jasmine.isDomNode(b)) { return a === b; } if (typeof a === "object" && typeof b === "object") { for (var property in b) { if (!hasKey(a, property) && hasKey(b, property)) { mismatchKeys.push("expected has key '" + property + "', but missing from actual."); } } for (property in a) { if (!hasKey(b, property) && hasKey(a, property)) { mismatchKeys.push("expected missing key '" + property + "', but present in actual."); } } for (property in b) { if (!equal(a[property], b[property])) { mismatchValues.push("'" + property + "' was

'" + (b[property] ? Jasmine.util.htmlEscape(b[property].toString()) : b[property]) + "'

in expected, but was

'" + (a[property] ? Jasmine.util.htmlEscape(a[property].toString()) : a[property]) + "'

in actual.
"); } } return (mismatchKeys.length == 0 && mismatchValues.length == 0); } if (b instanceof Jasmine.Any) { return b.matches(a); } // functions are considered equivalent if their bodies are equal // todo: remove this if (typeof a === "function" && typeof b === "function") { return a.toString() == b.toString(); } //Straight check return (a === b); }; var formatMismatches = function(name, array) { if (array.length == 0) return ''; var errorOutput = '

Different ' + name + ':
'; for (var i = 0; i < array.length; i++) { errorOutput += array[i] + '
'; } return errorOutput; }; return this.report(equal(this.actual, expected), 'Expected

' + Jasmine.pp(expected) + '

but got

' + Jasmine.pp(this.actual) + '
' + formatMismatches('Keys', mismatchKeys) + formatMismatches('Values', mismatchValues)); }; /** @deprecated */ Jasmine.Matchers.prototype.should_equal = Jasmine.Matchers.prototype.toEqual; Jasmine.Matchers.prototype.toNotEqual = function(expected) { return this.report((this.actual !== expected), 'Expected ' + Jasmine.pp(expected) + ' to not equal ' + Jasmine.pp(this.actual) + ', but it does.'); }; /** @deprecated */ Jasmine.Matchers.prototype.should_not_equal = Jasmine.Matchers.prototype.toNotEqual; Jasmine.Matchers.prototype.toMatch = function(reg_exp) { return this.report((new RegExp(reg_exp).test(this.actual)), 'Expected ' + this.actual + ' to match ' + reg_exp + '.'); }; /** @deprecated */ Jasmine.Matchers.prototype.should_match = Jasmine.Matchers.prototype.toMatch; Jasmine.Matchers.prototype.toNotMatch = function(reg_exp) { return this.report((!new RegExp(reg_exp).test(this.actual)), 'Expected ' + this.actual + ' to not match ' + reg_exp + '.'); }; /** @deprecated */ Jasmine.Matchers.prototype.should_not_match = Jasmine.Matchers.prototype.toNotMatch; Jasmine.Matchers.prototype.toBeDefined = function() { return this.report((this.actual !== undefined), 'Expected a value to be defined but it was undefined.'); }; /** @deprecated */ Jasmine.Matchers.prototype.should_be_defined = Jasmine.Matchers.prototype.toBeDefined; Jasmine.Matchers.prototype.toBeNull = function() { return this.report((this.actual === null), 'Expected a value to be null but it was not.'); }; /** @deprecated */ Jasmine.Matchers.prototype.should_be_null = Jasmine.Matchers.prototype.toBeNull; Jasmine.Matchers.prototype.toBeTruthy = function() { return this.report((this.actual), 'Expected a value to be truthy but it was not.'); }; /** @deprecated */ Jasmine.Matchers.prototype.should_be_truthy = Jasmine.Matchers.prototype.toBeTruthy; Jasmine.Matchers.prototype.toBeFalsy = function() { return this.report((!this.actual), 'Expected a value to be falsy but it was not.'); }; /** @deprecated */ Jasmine.Matchers.prototype.should_be_falsy = Jasmine.Matchers.prototype.toBeFalsy; Jasmine.Matchers.prototype.wasCalled = function() { if (!this.actual.isSpy) { return this.report(false, 'Expected value to be a spy, but it was not.'); } return this.report((this.actual.wasCalled), 'Expected spy to have been called, but it was not.'); }; /** @deprecated */ Jasmine.Matchers.prototype.was_called = Jasmine.Matchers.prototype.wasCalled; Jasmine.Matchers.prototype.wasNotCalled = function() { if (!this.actual.isSpy) { return this.report(false, 'Expected value to be a spy, but it was not.'); } return this.report((!this.actual.wasCalled), 'Expected spy to not have been called, but it was.'); }; /** @deprecated */ Jasmine.Matchers.prototype.was_not_called = Jasmine.Matchers.prototype.wasNotCalled; Jasmine.Matchers.prototype.wasCalledWith = function() { if (!this.wasCalled()) return false; var argMatcher = new Jasmine.Matchers(this.actual.mostRecentCall.args, this.results); return argMatcher.toEqual(Jasmine.util.argsToArray(arguments)); }; /** @deprecated */ Jasmine.Matchers.prototype.was_called = Jasmine.Matchers.prototype.wasCalled; Jasmine.Matchers.prototype.toContain = function(item) { return this.report((this.actual.indexOf(item) >= 0), 'Expected ' + Jasmine.pp(this.actual) + ' to contain ' + Jasmine.pp(item) + ', but it does not.'); }; Jasmine.Matchers.prototype.toNotContain = function(item) { return this.report((this.actual.indexOf(item) < 0), 'Expected ' + Jasmine.pp(this.actual) + ' not to contain ' + Jasmine.pp(item) + ', but it does.'); }; Jasmine.createSpy = function() { var spyObj = function() { spyObj.wasCalled = true; spyObj.callCount++; var args = Jasmine.util.argsToArray(arguments); spyObj.mostRecentCall = { object: this, args: args }; spyObj.argsForCall.push(args); return spyObj.plan.apply(this, arguments); }; spyObj.isSpy = true; spyObj.plan = function() { }; spyObj.andCallThrough = function() { spyObj.plan = spyObj.originalValue; return spyObj; }; spyObj.andReturn = function(value) { spyObj.plan = function() { return value; }; return spyObj; }; spyObj.andThrow = function(exceptionMsg) { spyObj.plan = function() { throw exceptionMsg; }; return spyObj; }; spyObj.andCallFake = function(fakeFunc) { spyObj.plan = fakeFunc; return spyObj; }; spyObj.reset = function() { spyObj.wasCalled = false; spyObj.callCount = 0; spyObj.argsForCall = []; spyObj.mostRecentCall = {}; }; spyObj.reset(); return spyObj; }; Jasmine.spyOn = function(obj, methodName) { var spec = Jasmine.getEnv().currentSpec; spec.after(function() { spec.removeAllSpies(); }); if (obj == undefined) { throw "spyOn could not find an object to spy upon"; } if (obj[methodName] === undefined) { throw methodName + '() method does not exist'; } if (obj[methodName].isSpy) { throw new Error(methodName + ' has already been spied upon'); } var spyObj = Jasmine.createSpy(); spec.spies_.push(spyObj); spyObj.baseObj = obj; spyObj.methodName = methodName; spyObj.originalValue = obj[methodName]; obj[methodName] = spyObj; return spyObj; }; var spyOn = Jasmine.spyOn; /* * Jasmine spec constructor */ Jasmine.Spec = function(description) { this.suite = null; this.description = description; this.queue = []; this.currentTimeout = 0; this.currentLatchFunction = undefined; this.finished = false; this.afterCallbacks = []; this.spies_ = []; this.results = new Jasmine.NestedResults(); }; Jasmine.Spec.prototype.freezeSuite = function(suite) { this.suite = suite; }; /** @deprecated */ Jasmine.Spec.prototype.expects_that = function(actual) { return new Jasmine.Matchers(actual, this.results); }; Jasmine.Spec.prototype.waits = function(timeout) { this.currentTimeout = timeout; this.currentLatchFunction = undefined; return this; }; Jasmine.Spec.prototype.waitsFor = function(timeout, latchFunction, message) { this.currentTimeout = timeout; this.currentLatchFunction = latchFunction; this.currentLatchFunction.description = message; return this; }; Jasmine.Spec.prototype.resetTimeout = function() { this.currentTimeout = 0; this.currentLatchFunction = undefined; }; Jasmine.Spec.prototype.finishCallback = function() { if (Jasmine.getEnv().reporter) { Jasmine.getEnv().reporter.reportSpecResults(this.results); } }; Jasmine.Spec.prototype.finish = function() { if (this.suite.afterEach) { Jasmine.safeExecuteBeforeOrAfter(this, this.suite.afterEach); } this.finishCallback(); this.finished = true; }; Jasmine.Spec.prototype.after = function(doAfter) { this.afterCallbacks.push(doAfter); }; Jasmine.Spec.prototype.execute = function() { Jasmine.getEnv().currentSpec = this; if (this.suite.beforeEach) { Jasmine.safeExecuteBeforeOrAfter(this, this.suite.beforeEach); } if (this.queue[0]) { this.queue[0].execute(); } else { this.finish(); } }; Jasmine.Spec.prototype.explodes = function() { throw 'explodes function should not have been called'; }; Jasmine.Spec.prototype.spyOn = Jasmine.spyOn; Jasmine.Spec.prototype.removeAllSpies = function() { for (var i = 0; i < this.spies_.length; i++) { var spy = this.spies_[i]; spy.baseObj[spy.methodName] = spy.originalValue; } this.spies_ = []; }; var it = function(description, func) { var that = new Jasmine.Spec(description); var addToQueue = function(func) { var currentFunction = new Jasmine.QueuedFunction(func, that.currentTimeout, that.currentLatchFunction, that); that.queue.push(currentFunction); if (that.queue.length > 1) { var previousFunction = that.queue[that.queue.length - 2]; previousFunction.next = function() { currentFunction.execute(); }; } that.resetTimeout(); return that; }; that.expectationResults = that.results.results; that.runs = addToQueue; that.freezeSuite(Jasmine.getEnv().currentSuite); Jasmine.getEnv().currentSuite.specs.push(that); Jasmine.getEnv().currentSpec = that; if (func) { addToQueue(func); } that.results.description = description; return that; }; //this mirrors the spec syntax so you can define a spec description that will not run. var xit = function() { return {runs: function() { } }; }; var expect = function() { return Jasmine.getEnv().currentSpec.expects_that.apply(Jasmine.getEnv().currentSpec, arguments); }; var runs = function(func) { Jasmine.getEnv().currentSpec.runs(func); }; var waits = function(timeout) { Jasmine.getEnv().currentSpec.waits(timeout); }; var waitsFor = function(timeout, latchFunction, message) { Jasmine.getEnv().currentSpec.waitsFor(timeout, latchFunction, message); }; var beforeEach = function(beforeEach) { beforeEach.typeName = 'beforeEach'; Jasmine.getEnv().currentSuite.beforeEach = beforeEach; }; var afterEach = function(afterEach) { afterEach.typeName = 'afterEach'; Jasmine.getEnv().currentSuite.afterEach = afterEach; }; Jasmine.Description = function(description, specDefinitions) { Jasmine.ActionCollection.call(this); this.description = description; this.specs = this.actions; }; Jasmine.util.inherit(Jasmine.Description, Jasmine.ActionCollection); var describe = function(description, spec_definitions) { var that = new Jasmine.Description(description, spec_definitions); Jasmine.getEnv().currentSuite = that; Jasmine.getEnv().currentRunner.suites.push(that); spec_definitions(); that.results.description = description; that.specResults = that.results.results; that.finishCallback = function() { if (Jasmine.getEnv().reporter) { Jasmine.getEnv().reporter.reportSuiteResults(that.results); } }; return that; }; var xdescribe = function() { return {execute: function() { }}; }; Jasmine.Runner = function() { Jasmine.ActionCollection.call(this); this.suites = this.actions; this.results.description = 'All Jasmine Suites'; }; Jasmine.util.inherit(Jasmine.Runner, Jasmine.ActionCollection); var Runner = function() { var that = new Jasmine.Runner(); that.finishCallback = function() { if (Jasmine.getEnv().reporter) { Jasmine.getEnv().reporter.reportRunnerResults(that.results); } }; that.suiteResults = that.results.results; Jasmine.getEnv().currentRunner = that; return that; }; Jasmine.getEnv().currentRunner = Runner(); /* JasmineReporters.reporter * Base object that will get called whenever a Spec, Suite, or Runner is done. It is up to * descendants of this object to do something with the results (see json_reporter.js) */ Jasmine.Reporters = {}; Jasmine.Reporters.reporter = function(callbacks) { var that = { callbacks: callbacks || {}, doCallback: function(callback, results) { if (callback) { callback(results); } }, reportRunnerResults: function(results) { that.doCallback(that.callbacks.runnerCallback, results); }, reportSuiteResults: function(results) { that.doCallback(that.callbacks.suiteCallback, results); }, reportSpecResults: function(results) { that.doCallback(that.callbacks.specCallback, results); } }; return that; }; Jasmine.util.formatException = function(e) { var lineNumber; if (e.line) { lineNumber = e.line; } else if (e.lineNumber) { lineNumber = e.lineNumber; } var file; if (e.sourceURL) { file = e.sourceURL; } else if (e.fileName) { file = e.fileName; } var message = (e.name && e.message) ? (e.name + ': ' + e.message) : e.toString(); if (file && lineNumber) { message += ' in ' + file + ' (line ' + lineNumber + ')'; } return message; }; Jasmine.util.htmlEscape = function(str) { if (!str) return str; return str.replace(/&/g, '&') .replace(//g, '>'); }; Jasmine.util.argsToArray = function(args) { var arrayOfArgs = []; for (var i = 0; i < args.length; i++) arrayOfArgs.push(args[i]); return arrayOfArgs; };