From dd8f248f7bfb9c4183e5225ede3b181e556f17df Mon Sep 17 00:00:00 2001 From: ragaskar Date: Sun, 1 Mar 2009 05:34:00 -0800 Subject: [PATCH] Major jasmine updates --- README.markdown | 10 +- example/example.html | 4 +- example/example.js | 10 +- jasmine.iml | 17 - jasmine.ipr | 250 ------ jasmine.iws | 565 ------------- lib/jasmine.js | 1033 +++++++++++++++++------- lib/json_reporter.js | 26 +- test/bootstrap.html | 4 +- test/bootstrap.js | 1597 ++++++++++++++++++++++++++++--------- test/jsUnitMockTimeout.js | 81 ++ test/json2.js | 478 +++++++++++ 12 files changed, 2565 insertions(+), 1510 deletions(-) delete mode 100755 jasmine.iml delete mode 100755 jasmine.ipr delete mode 100644 jasmine.iws create mode 100755 test/jsUnitMockTimeout.js create mode 100644 test/json2.js diff --git a/README.markdown b/README.markdown index a887c30..3d39065 100644 --- a/README.markdown +++ b/README.markdown @@ -181,7 +181,8 @@ You can only have one instance of Jasmine (which is a container for a runner) ru This is a bit sloppy and will be fixed at some point - but it allows for a nicer syntax when defining your specs. For now we expect this to be fine as most of the time having multiple suites is sufficient for isolating application-level code. -## Contributing and Tests +Contributing and Tests +---------------------- Sometimes it's hard to test a framework with the framework itself. Either the framework isn't mature enough or it just hurts your head. Jasmine is affected by both. @@ -189,13 +190,6 @@ So we made a little bootstrappy test reporter that lets us test Jasmine's pieces Your contributions are welcome. Please submit tests with your pull request. -### Mailing List -[http://groups.google.com/group/pivotallabsopensource](http://groups.google.com/group/pivotallabsopensource) - -### Pivotal Tracker Bug/Feature Tracker -[http://www.pivotaltracker.com/projects/10606](http://www.pivotaltracker.com/projects/10606) - - ## Maintainers * [Davis W. Frank](dwfrank@pivotallabs.com), Pivotal Labs diff --git a/example/example.html b/example/example.html index 6fa607a..1776bfb 100644 --- a/example/example.html +++ b/example/example.html @@ -13,10 +13,10 @@
diff --git a/example/example.js b/example/example.js index 74fb27f..1c72179 100644 --- a/example/example.js +++ b/example/example.js @@ -1,7 +1,7 @@ describe('one suite description', function () { - it('should be a test', function() { - runs(function () { - this.expects_that(true).should_equal(true); - }); + it('should be a test', function() { + runs(function () { + this.expects_that(true).should_equal(true); }); - }); \ No newline at end of file + }); +}); \ No newline at end of file diff --git a/jasmine.iml b/jasmine.iml deleted file mode 100755 index bf91408..0000000 --- a/jasmine.iml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/jasmine.ipr b/jasmine.ipr deleted file mode 100755 index ce1553f..0000000 --- a/jasmine.ipr +++ /dev/null @@ -1,250 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/jasmine.iws b/jasmine.iws deleted file mode 100644 index 69750ec..0000000 --- a/jasmine.iws +++ /dev/null @@ -1,565 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - localhost - 5050 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/jasmine.js b/lib/jasmine.js index 51480fb..00bb5e6 100755 --- a/lib/jasmine.js +++ b/lib/jasmine.js @@ -1,57 +1,53 @@ -// Crockford's helpers - -// Object.create instead of new Object -if (typeof Object.create !== 'function') { - Object.create = function (o) { - var F = function () { - }; - F.prototype = o; - return new F(); - }; -} - -// Klass.method instead of Klass.prototype.name = function -if (typeof Function.method !== 'function') { - Function.prototype.method = function (name, func) { - this.prototype[name] = func; - return this; - } -} - /* * 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; +}; + /* - * object for holding results; allows for the results array to hold another nestedResults() - * + * Holds results; allows for the results array to hold another Jasmine.NestedResults */ -var nestedResults = function() { - var that = { - totalCount: 0, - passedCount: 0, - failedCount: 0, - results: [], +Jasmine.NestedResults = function() { + this.totalCount = 0; + this.passedCount = 0; + this.failedCount = 0; + this.results = []; +}; - rollupCounts: function (result) { - that.totalCount += result.totalCount; - that.passedCount += result.passedCount; - that.failedCount += result.failedCount; - }, +Jasmine.NestedResults.prototype.rollupCounts = function(result) { + this.totalCount += result.totalCount; + this.passedCount += result.passedCount; + this.failedCount += result.failedCount; +}; - push: function (result) { - if (result.results) { - that.rollupCounts(result); - } else { - that.totalCount++; - result.passed ? that.passedCount++ : that.failedCount++; - } - that.results.push(result); +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); +}; - return that; -} +Jasmine.NestedResults.prototype.passed = function() { + return this.passedCount === this.totalCount; +}; /* @@ -60,135 +56,291 @@ var nestedResults = function() { * action. * **/ -var actionCollection = function () { - var that = { - actions: [], - index: 0, - finished: false, - results: nestedResults(), +Jasmine.ActionCollection = function() { + this.actions = []; + this.index = 0; + this.finished = false; + this.results = new Jasmine.NestedResults(); +}; - finish: function () { - if (that.finishCallback) { - that.finishCallback(); +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); } - that.finished = true; - }, - - report: function (result) { - that.results.push(result); - }, - - execute: function () { - if (that.actions.length > 0) { - that.next(); - } - }, - - getCurrentAction: function () { - return that.actions[that.index]; - }, - - next: function() { - if (that.index >= that.actions.length) { - that.finish(); - return; - } - - var currentAction = that.getCurrentAction(); - - if (that.beforeEach) { - that.beforeEach.apply(currentAction); - } - - currentAction.execute(); - that.waitForDone(currentAction); - }, - - waitForDone: function(action) { - var id = setInterval(function () { - if (action.finished) { - clearInterval(id); - that.report(action.results); - - if (that.afterEach) { - that.afterEach.apply(action); - } - - that.index++; - that.next(); - } - }, 150); } } - return that; -} + 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 actionCollection's actions are implemented + * QueuedFunction is how ActionCollections' actions are implemented */ -var queuedFunction = function(func, timeout, spec) { - var that = { - func: func, - next: function () { - spec.finish(); // default value is to be done after one function - }, - safeExecute: function () { - try { - that.func.apply(spec); - } - catch (e) { - spec.results.push({passed:false, message: Jasmine.Util.formatException(e)}); - } - }, - execute: function () { - if (timeout > 0) { - setTimeout(function () { - that.safeExecute(); - that.next(); - }, timeout); - } else { - that.safeExecute(); - that.next(); - } - } +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 + '...'); } - return that; -} + + 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; +}; -var Jasmine = {} +Jasmine.Env.prototype.execute = function() { + this.currentRunner.execute(); +}; -Jasmine.init = function () { - var that = { - currentSpec: null, - currentSuite: null, - currentRunner: null, - execute: function () { - that.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 that; -} + return '[ ' + formatted_value + ' ]'; +}; -var jasmine = Jasmine.init(); +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 with Jasmine.Matchers.method() - don't forget to write a test + * Jasmine.Matchers methods; add your own by extending Jasmine.Matchers.prototype - don't forget to write a test * */ -Jasmine.Matchers = function (actual, results) { +Jasmine.Matchers = function(actual, results) { this.actual = actual; - this.passing_message = 'Passed.' - this.results = results || nestedResults(); -} + this.passing_message = 'Passed.'; + this.results = results || new Jasmine.NestedResults(); +}; -Jasmine.Matchers.method('report', function (result, failing_message) { +Jasmine.Matchers.prototype.report = function(result, failing_message) { this.results.push({ passed: result, @@ -196,167 +348,486 @@ Jasmine.Matchers.method('report', function (result, failing_message) { }); return result; -}); +}; -Jasmine.Matchers.method('should_equal', function (expected) { - return this.report((this.actual === expected), - 'Expected ' + expected + ' but got ' + this.actual + '.'); -}); +Jasmine.isDomNode = function(obj) { + return obj['nodeType'] > 0; +}; -Jasmine.Matchers.method('should_not_equal', function (expected) { +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 ' + expected + ' to not equal ' + this.actual + ', but it does.'); -}); + '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.method('should_match', function (reg_exp) { - return this.report((reg_exp.match(this.actual)), +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.method('should_be_defined', function () { +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 ' + this.actual + ' to be defined.'); -}); + 'Expected a value to be defined but it was undefined.'); +}; +/** @deprecated */ +Jasmine.Matchers.prototype.should_be_defined = Jasmine.Matchers.prototype.toBeDefined; -Jasmine.Matchers.method('innerHTML_should_match', function (reg_exp) { - return this.report((reg_exp.match(this.actual.innerHTML)), - 'Expected ' + this.actual.innerHTML + ' to match ' + reg_exp + '.'); -}); +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 */ -var it = function (description, func) { - var that = { - description: description, - queue: [], - currentTimeout: 0, - finished: false, +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_ = []; - results: nestedResults(), + this.results = new Jasmine.NestedResults(); +}; - expects_that: function (actual) { - return new Jasmine.Matchers(actual, that.results); - }, +Jasmine.Spec.prototype.freezeSuite = function(suite) { + this.suite = suite; +}; - waits: function (timeout) { - that.currentTimeout = timeout; - return that; - }, +/** @deprecated */ +Jasmine.Spec.prototype.expects_that = function(actual) { + return new Jasmine.Matchers(actual, this.results); +}; - resetTimeout: function() { - that.currentTimeout = 0; - }, +Jasmine.Spec.prototype.waits = function(timeout) { + this.currentTimeout = timeout; + this.currentLatchFunction = undefined; + return this; +}; - finishCallback: function () { - if (jasmine.reporter) { - jasmine.reporter.reportSpecResults(that.results); - } - }, +Jasmine.Spec.prototype.waitsFor = function(timeout, latchFunction, message) { + this.currentTimeout = timeout; + this.currentLatchFunction = latchFunction; + this.currentLatchFunction.description = message; + return this; +}; - finish: function() { - that.finishCallback(); - that.finished = true; - }, +Jasmine.Spec.prototype.resetTimeout = function() { + this.currentTimeout = 0; + this.currentLatchFunction = undefined; +}; - execute: function () { - if (that.queue[0]) { - that.queue[0].execute(); - } - else { - that.finish(); - } - } - }; +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 = queuedFunction(func, that.currentTimeout, that); + 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 () { + previousFunction.next = function() { currentFunction.execute(); - } + }; } that.resetTimeout(); return that; - } + }; that.expectationResults = that.results.results; that.runs = addToQueue; + that.freezeSuite(Jasmine.getEnv().currentSuite); - jasmine.currentSuite.specs.push(that); - jasmine.currentSpec = that; + Jasmine.getEnv().currentSuite.specs.push(that); + + Jasmine.getEnv().currentSpec = that; if (func) { - func(); + addToQueue(func); } that.results.description = description; return that; -} +}; -var runs = function (func) { - jasmine.currentSpec.runs(func); -} +//this mirrors the spec syntax so you can define a spec description that will not run. +var xit = function() { + return {runs: function() { + } }; +}; -var waits = function (timeout) { - jasmine.currentSpec.waits(timeout); -} +var expect = function() { + return Jasmine.getEnv().currentSpec.expects_that.apply(Jasmine.getEnv().currentSpec, arguments); +}; -var beforeEach = function (beforeEach) { - jasmine.currentSuite.beforeEach = beforeEach; -} +var runs = function(func) { + Jasmine.getEnv().currentSpec.runs(func); +}; -var afterEach = function (afterEach) { - jasmine.currentSuite.afterEach = afterEach; -} +var waits = function(timeout) { + Jasmine.getEnv().currentSpec.waits(timeout); +}; -var describe = function (description, spec_definitions) { - var that = actionCollection(); +var waitsFor = function(timeout, latchFunction, message) { + Jasmine.getEnv().currentSpec.waitsFor(timeout, latchFunction, message); +}; - that.description = description; - that.specs = that.actions; +var beforeEach = function(beforeEach) { + beforeEach.typeName = 'beforeEach'; + Jasmine.getEnv().currentSuite.beforeEach = beforeEach; +}; - jasmine.currentSuite = that; - jasmine.currentRunner.suites.push(that); +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.reporter) { - jasmine.reporter.reportSuiteResults(that.results); + that.finishCallback = function() { + if (Jasmine.getEnv().reporter) { + Jasmine.getEnv().reporter.reportSuiteResults(that.results); } - } + }; return that; -} +}; -var Runner = function () { - var that = actionCollection(); +var xdescribe = function() { + return {execute: function() { + }}; +}; - that.suites = that.actions; - that.results.description = 'All Jasmine Suites'; +Jasmine.Runner = function() { + Jasmine.ActionCollection.call(this); - that.finishCallback = function () { - if (jasmine.reporter) { - jasmine.reporter.reportRunnerResults(that.results); + 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.currentRunner = that; + Jasmine.getEnv().currentRunner = that; return that; -} +}; -jasmine.currentRunner = Runner(); +Jasmine.getEnv().currentRunner = Runner(); /* JasmineReporters.reporter * Base object that will get called whenever a Spec, Suite, or Runner is done. It is up to @@ -364,58 +835,66 @@ jasmine.currentRunner = Runner(); */ Jasmine.Reporters = {}; -Jasmine.Reporters.reporter = function (callbacks) { +Jasmine.Reporters.reporter = function(callbacks) { var that = { callbacks: callbacks || {}, - doCallback: function (callback, results) { + doCallback: function(callback, results) { if (callback) { callback(results); } }, - reportRunnerResults: function (results) { + reportRunnerResults: function(results) { that.doCallback(that.callbacks.runnerCallback, results); }, - reportSuiteResults: function (results) { + reportSuiteResults: function(results) { that.doCallback(that.callbacks.suiteCallback, results); }, - reportSpecResults: function (results) { + reportSpecResults: function(results) { that.doCallback(that.callbacks.specCallback, results); } - } + }; return that; -} +}; -Jasmine.Util = { - formatException: function(e) { - // if (typeof e === 'String') { - // return 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; - if (file && lineNumber) { - message += ' in ' + file + ' (line ' + lineNumber + ')'; - } - - return message; +Jasmine.util.formatException = function(e) { + var lineNumber; + if (e.line) { + lineNumber = e.line; + } + else if (e.lineNumber) { + lineNumber = e.lineNumber; } -} \ No newline at end of file + 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; +}; diff --git a/lib/json_reporter.js b/lib/json_reporter.js index 18a6e96..07ab322 100644 --- a/lib/json_reporter.js +++ b/lib/json_reporter.js @@ -4,28 +4,32 @@ * result. Calling application can then do whatever it wants/needs with the string; */ Jasmine.Reporters.JSON = function () { + var toJSON = function(object){ + return JSON.stringify(object); + }; var that = Jasmine.Reporters.reporter(); that.specJSON = ''; that.suiteJSON = ''; that.runnerJSON = ''; var saveSpecResults = function (results) { - that.specJSON = Object.toJSON(results); - } + that.specJSON = toJSON(results); + }; that.reportSpecResults = saveSpecResults; var saveSuiteResults = function (results) { - that.suiteJSON = Object.toJSON(results); - } + that.suiteJSON = toJSON(results); + }; that.reportSuiteResults = saveSuiteResults; var saveRunnerResults = function (results) { - that.runnerJSON = Object.toJSON(results); - } + that.runnerJSON = toJSON(results); + }; that.reportRunnerResults = saveRunnerResults; + that.toJSON = toJSON; return that; -} +}; Jasmine.Reporters.domWriter = function (elementId) { var that = { @@ -36,12 +40,12 @@ Jasmine.Reporters.domWriter = function (elementId) { that.element.innerHTML += text; } } - } + }; that.element.innerHTML = ''; return that; -} +}; Jasmine.Reporters.JSONtoDOM = function (elementId) { var that = Jasmine.Reporters.JSON(); @@ -49,10 +53,10 @@ Jasmine.Reporters.JSONtoDOM = function (elementId) { that.domWriter = Jasmine.Reporters.domWriter(elementId); var writeRunnerResults = function (results) { - that.domWriter.write(Object.toJSON(results)); + that.domWriter.write(that.toJSON(results)); }; that.reportRunnerResults = writeRunnerResults; return that; -} +}; diff --git a/test/bootstrap.html b/test/bootstrap.html index 4c2ef58..d250512 100755 --- a/test/bootstrap.html +++ b/test/bootstrap.html @@ -3,7 +3,9 @@ Jasmine Tests - + + + diff --git a/test/bootstrap.js b/test/bootstrap.js index 2bd7f88..0e9d158 100755 --- a/test/bootstrap.js +++ b/test/bootstrap.js @@ -1,86 +1,431 @@ -// Bootstrap Test Reporter function -var reporter = function () { - - var total = 0; - var passes = 0; - var fails = 0; - - var that = { - test: function (result, message) { - total++; - - if (result) { - passes++; - iconElement = $('icons'); - iconElement.appendChild(new Element('img', {src: '../images/go-16.png'})); - } - else { - fails++; - var fails_report = $('fails'); - fails_report.show(); - - var iconElement = $('icons'); - iconElement.appendChild(new Element('img', {src: '../images/fail-16.png'})); - - var failMessages = $('fail_messages'); - var newFail = new Element('p', {'class': 'fail'}); - newFail.innerHTML = message; - failMessages.appendChild(newFail); - } - }, - - summary: function () { - var el = new Element('p', {'class': ((fails > 0) ? 'fail_in_summary' : '') }); - el.innerHTML = total + ' expectations, ' + passes + ' passing, ' + fails + ' failed.'; - - var summaryElement = $('results_summary'); - summaryElement.appendChild(el); - summaryElement.show(); - } +var createElement = function(tag, attrs) { + var element = document.createElement(tag); + for (var attr in attrs) { + element[attr] = attrs[attr]; } - return that; -}(); + return element; +}; + +// Bootstrap Test Reporter function +var Reporter = function () { + this.total = 0; + this.passes = 0; + this.fails = 0; + this.start = new Date(); +}; + +Reporter.prototype.toJSON = function(object) { + return JSON.stringify(object); +}; + +Reporter.prototype.test = function (result, message) { + this.total++; + + if (result) { + this.passes++; + iconElement = document.getElementById('icons'); + iconElement.appendChild(createElement('img', {src: '../images/go-16.png'})); + } + else { + this.fails++; + var fails_report = document.getElementById('fails'); + fails_report.style.display = ""; + + var iconElement = document.getElementById('icons'); + iconElement.appendChild(createElement('img', {src: '../images/fail-16.png'})); + + var failMessages = document.getElementById('fail_messages'); + var newFail = createElement('p', {'class': 'fail'}); + newFail.innerHTML = message; + failMessages.appendChild(newFail); + } +}; + +Reporter.prototype.summary = function () { + var el = createElement('p', {'class': ((this.fails > 0) ? 'fail_in_summary' : '') }); + el.innerHTML = this.total + ' expectations, ' + this.passes + ' passing, ' + this.fails + ' failed in ' + (new Date().getTime() - this.start.getTime()) + "ms."; + + var summaryElement = document.getElementById('results_summary'); + summaryElement.appendChild(el); + summaryElement.style.display = ""; +}; + + +var reporter = new Reporter(); var testMatchersComparisons = function () { var expected = new Jasmine.Matchers(true); - reporter.test(expected.should_equal(true), - 'expects_that(true).should_equal(true) returned false'); + reporter.test(expected.toEqual(true), + 'expect(true).toEqual(true) returned false'); + + expected = new Jasmine.Matchers({foo:'bar'}); + reporter.test(!expected.toEqual(null), + 'expect({foo:\'bar\'}).toEqual(null) returned true'); + + var functionA = function () { return 'hi'; }; + var functionB = function () { return 'hi'; }; + expected = new Jasmine.Matchers({foo:functionA}); + reporter.test(expected.toEqual({foo:functionB}), + 'expect({foo: function () { return \'hi\' };})' + + '.toEqual({foo: function () { return \'hi\' };}) ' + + 'returned true, but returned false'); expected = new Jasmine.Matchers(false); - reporter.test(!(expected.should_equal(true)), - 'expects_that(true).should_equal(true) returned true'); + reporter.test(!(expected.toEqual(true)), + 'expect(false).toEqual(true) returned true'); + + var nodeA = document.createElement('div'); + var nodeB = document.createElement('div'); + expected = new Jasmine.Matchers(nodeA); + reporter.test((expected.toEqual(nodeA)), + 'expect(nodeA).toEqual(nodeA) returned false'); + + expected = new Jasmine.Matchers(nodeA); + reporter.test(!(expected.toEqual(nodeB)), + 'expect(nodeA).toEqual(nodeB) returned true'); + expected = new Jasmine.Matchers(true); - reporter.test(expected.should_not_equal(false), - 'expects_that(true).should_not_equal(false) retruned false'); + reporter.test(expected.toNotEqual(false), + 'expect(true).toNotEqual(false) returned false'); expected = new Jasmine.Matchers(true); - reporter.test(!(expected.should_not_equal(true)), - 'expects_that(true).should_not_equal(false) retruned true'); + reporter.test(!(expected.toNotEqual(true)), + 'expect(true).toNotEqual(false) returned true'); expected = new Jasmine.Matchers('foobarbel'); - reporter.test((expected.should_match(/bar/)), - 'expects_that(forbarbel).should_match(/bar/) returned false'); + reporter.test((expected.toMatch(/bar/)), + 'expect(forbarbel).toMatch(/bar/) returned false'); expected = new Jasmine.Matchers('foobazbel'); - reporter.test(!(expected.should_match(/bar/)), - 'expects_that(forbazbel).should_match(/bar/) returned true'); + reporter.test(!(expected.toMatch(/bar/)), + 'expect(forbazbel).toMatch(/bar/) returned true'); + + expected = new Jasmine.Matchers('foobarbel'); + reporter.test((expected.toMatch("bar")), + 'expect(forbarbel).toMatch(/bar/) returned false'); + + expected = new Jasmine.Matchers('foobazbel'); + reporter.test(!(expected.toMatch("bar")), + 'expect(forbazbel).toMatch(/bar/) returned true'); + + expected = new Jasmine.Matchers('foobarbel'); + reporter.test(!(expected.toNotMatch(/bar/)), + 'expect(forbarbel).toNotMatch(/bar/) returned false'); + + expected = new Jasmine.Matchers('foobazbel'); + reporter.test((expected.toNotMatch(/bar/)), + 'expect(forbazbel).toNotMatch(/bar/) returned true'); + + expected = new Jasmine.Matchers('foobarbel'); + reporter.test(!(expected.toNotMatch("bar")), + 'expect(forbarbel).toNotMatch("bar") returned false'); + + expected = new Jasmine.Matchers('foobazbel'); + reporter.test((expected.toNotMatch("bar")), + 'expect(forbazbel).toNotMatch("bar") returned true'); expected = new Jasmine.Matchers('foo'); - reporter.test(expected.should_be_defined(), - 'expects_that(foo).should_be_defined() returned true'); + reporter.test(expected.toBeDefined(), + 'expect(foo).toBeDefined() returned true'); expected = new Jasmine.Matchers(undefined); - reporter.test(! expected.should_be_defined(), - 'expects_that(undefined).should_be_defined() returned false'); -} + reporter.test(! expected.toBeDefined(), + 'expect(undefined).toBeDefined() returned false'); + + expected = new Jasmine.Matchers(null); + reporter.test(expected.toBeNull(), + 'expect(null).toBeNull() should be true'); + + expected = new Jasmine.Matchers(undefined); + reporter.test(! expected.toBeNull(), + 'expect(undefined).toBeNull() should be false'); + + expected = new Jasmine.Matchers("foo"); + reporter.test(! expected.toBeNull(), + 'expect("foo").toBeNull() should be false'); + + expected = new Jasmine.Matchers(false); + reporter.test(expected.toBeFalsy(), + 'expect(false).toBeFalsy() should be true'); + + expected = new Jasmine.Matchers(true); + reporter.test(!expected.toBeFalsy(), + 'expect(true).toBeFalsy() should be false'); + + expected = new Jasmine.Matchers(undefined); + reporter.test(expected.toBeFalsy(), + 'expect(undefined).toBeFalsy() should be true'); + + expected = new Jasmine.Matchers(0); + reporter.test(expected.toBeFalsy(), + 'expect(0).toBeFalsy() should be true'); + + expected = new Jasmine.Matchers(""); + reporter.test(expected.toBeFalsy(), + 'expect("").toBeFalsy() should be true'); + + expected = new Jasmine.Matchers(false); + reporter.test(!expected.toBeTruthy(), + 'expect(false).toBeTruthy() should be false'); + + expected = new Jasmine.Matchers(true); + reporter.test(expected.toBeTruthy(), + 'expect(true).toBeTruthy() should be true'); + + expected = new Jasmine.Matchers(undefined); + reporter.test(!expected.toBeTruthy(), + 'expect(undefined).toBeTruthy() should be false'); + + expected = new Jasmine.Matchers(0); + reporter.test(!expected.toBeTruthy(), + 'expect(0).toBeTruthy() should be false'); + + expected = new Jasmine.Matchers(""); + reporter.test(!expected.toBeTruthy(), + 'expect("").toBeTruthy() should be false'); + + expected = new Jasmine.Matchers("hi"); + reporter.test(expected.toBeTruthy(), + 'expect("hi").toBeTruthy() should be true'); + + expected = new Jasmine.Matchers(5); + reporter.test(expected.toBeTruthy(), + 'expect(5).toBeTruthy() should be true'); + + expected = new Jasmine.Matchers({foo: 1}); + reporter.test(expected.toBeTruthy(), + 'expect({foo: 1}).toBeTruthy() should be true'); + + expected = new Jasmine.Matchers(undefined); + reporter.test(expected.toEqual(undefined), + 'expect(undefined).toEqual(undefined) should return true'); + + expected = new Jasmine.Matchers({foo:'bar'}); + reporter.test(expected.toEqual({foo:'bar'}), + 'expect({foo:\'bar\').toEqual({foo:\'bar\'}) returned true'); + + expected = new Jasmine.Matchers("foo"); + reporter.test(! expected.toEqual({bar: undefined}), + 'expect({"foo").toEqual({bar:undefined}) should return false'); + + expected = new Jasmine.Matchers({foo: undefined}); + reporter.test(! expected.toEqual("goo"), + 'expect({foo:undefined}).toEqual("goo") should return false'); + + expected = new Jasmine.Matchers({foo: {bar :undefined}}); + reporter.test(! expected.toEqual("goo"), + 'expect({foo:{ bar: undefined}}).toEqual("goo") should return false'); + + expected = new Jasmine.Matchers("foo"); + reporter.test(expected.toEqual(Jasmine.any(String)), + 'expect("foo").toEqual(Jasmine.any(String)) should return true'); + + expected = new Jasmine.Matchers(3); + reporter.test(expected.toEqual(Jasmine.any(Number)), + 'expect(3).toEqual(Jasmine.any(Number)) should return true'); + + expected = new Jasmine.Matchers("foo"); + reporter.test(! expected.toEqual(Jasmine.any(Function)), + 'expect("foo").toEqual(Jasmine.any(Function)) should return false'); + + expected = new Jasmine.Matchers(["foo", "goo"]); + reporter.test(expected.toEqual(["foo", Jasmine.any(String)]), + 'expect(["foo", "goo"]).toEqual(["foo", Jasmine.any(String)]) should return true'); + + expected = new Jasmine.Matchers(function () {}); + reporter.test(expected.toEqual(Jasmine.any(Function)), + 'expect(function () {}).toEqual(Jasmine.any(Function)) should return true'); + + + + expected = new Jasmine.Matchers({foo: "bar", baz: undefined}); + reporter.test(expected.toEqual({foo: "bar", baz: undefined}), + 'expect({foo: "bar", baz: undefined}).toEqual({foo: "bar", baz: undefined}) should return true'); + + expected = new Jasmine.Matchers({foo:['bar','baz','quux']}); + reporter.test(expected.toEqual({foo:['bar','baz','quux']}), + "expect({foo:['bar','baz','quux']}).toEqual({foo:['bar','baz','quux']}) returned true"); + + expected = new Jasmine.Matchers({foo: {bar:'baz'}, quux:'corge'}); + reporter.test(expected.toEqual({foo:{bar:'baz'}, quux:'corge'}), + 'expect({foo:{bar:"baz"}, quux:"corge"}).toEqual({foo: {bar: \'baz\'}, quux:\'corge\'}) returned true'); + + expected = new Jasmine.Matchers({x:"x", y:"y", z:"w"}); + reporter.test(expected.toNotEqual({x:"x", y:"y", z:"z"}), + 'expect({x:"x", y:"y", z:"w"}).toNotEqual({x:"x", y:"y", z:"w"}) returned true'); + + expected = new Jasmine.Matchers({x:"x", y:"y", w:"z"}); + reporter.test(expected.toNotEqual({x:"x", y:"y", z:"z"}), + 'expect({x:"x", y:"y", w:"z"}).toNotEqual({x:"x", y:"y", z:"z"}) returned true'); + + expected = new Jasmine.Matchers({x:"x", y:"y", z:"z"}); + reporter.test(expected.toNotEqual({w: "w", x:"x", y:"y", z:"z"}), + 'expect({x:"x", y:"y", z:"z"}).toNotEqual({w: "w", x:"x", y:"y", z:"z"}) returned true'); + + expected = new Jasmine.Matchers({w: "w", x:"x", y:"y", z:"z"}); + reporter.test(expected.toNotEqual({x:"x", y:"y", z:"z"}), + 'expect({w: "w", x:"x", y:"y", z:"z"}).toNotEqual({x:"x", y:"y", z:"z"}) returned true'); + + expected = new Jasmine.Matchers([1, "A"]); + reporter.test(expected.toEqual([1, "A"]), + 'expect([1, "A"]).toEqual([1, "A"]) returned true'); + + + var expected = new Jasmine.Matchers(['A', 'B', 'C']); + reporter.test(expected.toContain('A'), + 'expect(["A", "B", "C").toContain("A") returned false'); + reporter.test(!expected.toContain('F'), + 'expect(["A", "B", "C").toContain("F") returned true'); + reporter.test(expected.toNotContain('F'), + 'expect(["A", "B", "C").toNotContain("F") returned false'); + reporter.test(!expected.toNotContain('A'), + 'expect(["A", "B", "C").toNotContain("A") returned true'); + + var currentSuite = describe('default current suite', function() { + }); + var spec = it(); + var TestClass = { someFunction: function() { + } }; + + expected = new Jasmine.Matchers(TestClass.someFunction); + reporter.test(!expected.wasCalled(), + 'expect(TestClass.someFunction).wasCalled() returned true for non-spies, expected false'); + expected = new Jasmine.Matchers(TestClass.someFunction); + reporter.test(!expected.wasNotCalled(), + 'expect(TestClass.someFunction).wasNotCalled() returned true for non-spies, expected false'); + + spec.spyOn(TestClass, 'someFunction'); + + expected = new Jasmine.Matchers(TestClass.someFunction); + reporter.test(!expected.wasCalled(), + 'expect(TestClass.someFunction).wasCalled() returned true when spies have not been called, expected false'); + expected = new Jasmine.Matchers(TestClass.someFunction); + reporter.test(expected.wasNotCalled(), + 'expect(TestClass.someFunction).wasNotCalled() returned false when spies have not been called, expected true'); + + TestClass.someFunction(); + expected = new Jasmine.Matchers(TestClass.someFunction); + reporter.test(expected.wasCalled(), + 'expect(TestClass.someFunction).wasCalled() returned false when spies have been called, expected true'); + expected = new Jasmine.Matchers(TestClass.someFunction); + reporter.test(!expected.wasNotCalled(), + 'expect(TestClass.someFunction).wasNotCalled() returned true when spies have been called, expected false'); + + TestClass.someFunction('a', 'b', 'c'); + expected = new Jasmine.Matchers(TestClass.someFunction); + reporter.test(expected.wasCalledWith('a', 'b', 'c'), + 'expect(TestClass.someFunction).wasCalledWith(\'a\', \'b\', \'c\') returned false, expected true'); + + expected = new Jasmine.Matchers(TestClass.someFunction); + reporter.test(!expected.wasCalledWith('c', 'b', 'a'), + 'expect(TestClass.someFunction).wasCalledWith(\'c\', \'b\', \'a\') returned true, expected false'); + // todo: make this better... only one result should be added. [xian/rajan 20090310] + reporter.test(expected.results.results[0].passed, + 'result 0 (from wasCalled()) should be true'); + reporter.test(!expected.results.results[1].passed, + 'result 1 (from arguments comparison) should be false'); +}; + +var testMatchersPrettyPrinting = function () { + var sampleValue; + var expected; + var actual; + + sampleValue = 'some string'; + reporter.test((Jasmine.pp(sampleValue) === "'some string'"), + "Expected Jasmine.pp('some string') to return the string 'some string' but got " + Jasmine.pp(sampleValue)); + + sampleValue = true; + reporter.test((Jasmine.pp(sampleValue) === 'true'), + "Expected Jasmine.pp(true) to return the string 'true' but got " + Jasmine.pp(sampleValue)); + + sampleValue = false; + reporter.test((Jasmine.pp(sampleValue) === 'false'), + "Expected Jasmine.pp(false) to return the string 'false' but got " + Jasmine.pp(sampleValue)); + + sampleValue = null; + reporter.test((Jasmine.pp(sampleValue) === 'null'), + "Expected Jasmine.pp(null) to return the string 'null' but got " + Jasmine.pp(sampleValue)); + + sampleValue = undefined; + reporter.test((Jasmine.pp(sampleValue) === 'undefined'), + "Expected Jasmine.pp(undefined) to return the string 'undefined' but got " + Jasmine.pp(sampleValue)); + + sampleValue = 3; + reporter.test((Jasmine.pp(sampleValue) === '3'), + "Expected Jasmine.pp(3) to return the string '3' but got " + Jasmine.pp(sampleValue)); + + sampleValue = [1, 2]; + reporter.test((Jasmine.pp(sampleValue) === '[ 1, 2 ]'), + "Expected Jasmine.pp([ 1, 2 ]) to return the string '[ 1, 2 ]' but got " + Jasmine.pp(sampleValue)); + + var array1 = [1, 2]; + var array2 = [array1]; + array1.push(array2); + sampleValue = array1; + expected = '[ 1, 2, [ ] ]'; + actual = Jasmine.pp(sampleValue); + reporter.test(actual === expected, + "Expected Jasmine.pp([ 1, 2, Array ]) to return the string " + '"' + expected + '"' + " but got " + actual); + + sampleValue = [1, 'foo', {}, undefined, null]; + expected = "[ 1, 'foo', { }, undefined, null ]"; + actual = Jasmine.pp(sampleValue); + reporter.test(actual === expected, + "Expected Jasmine.pp([ 1, 'foo', Object, undefined, null ]) to return the string " + '"' + expected + '"' + " but got " + actual); + + sampleValue = window; + expected = ""; + actual = Jasmine.pp(sampleValue); + reporter.test(actual === expected, + "Expected Jasmine.pp() to return the string " + '"' + expected + '"' + " but got " + actual); + + sampleValue = {foo: 'bar'}; + expected = "{ foo : 'bar' }"; + actual = Jasmine.pp(sampleValue); + reporter.test(actual === expected, + "Expected Jasmine.pp({ foo : 'bar' }) to return the string " + '"' + expected + '"' + " but got " + actual); + + sampleValue = {foo:'bar', baz:3, nullValue: null, undefinedValue: undefined}; + expected = "{ foo : 'bar', baz : 3, nullValue : null, undefinedValue : undefined }"; + actual = Jasmine.pp(sampleValue); + reporter.test(actual === expected, + "Expected Jasmine.pp(" + '"' + "{ foo : 'bar', baz : 3, nullValue : null, undefinedValue : undefined }" + '"' + " to return the string " + '"' + expected + '"' + " but got " + actual); + + sampleValue = {foo: function () { + }, bar: [1, 2, 3]}; + expected = "{ foo : Function, bar : [ 1, 2, 3 ] }"; + actual = Jasmine.pp(sampleValue); + reporter.test(actual === expected, + "Expected Jasmine.pp(" + '"' + "{ foo : function () {}, bar : [1, 2, 3] }" + '"' + " to return the string " + '"' + expected + '"' + " but got " + actual); + + sampleValue = {foo: 'hello'}; + sampleValue.nested = sampleValue; + expected = "{ foo : 'hello', nested : }"; + actual = Jasmine.pp(sampleValue); + reporter.test(actual === expected, + "Expected Jasmine.pp({foo: \'hello\'}) to return the string " + '"' + expected + '"' + " but got " + actual); + + var sampleNode = document.createElement('div'); + sampleNode.innerHTML = 'foobar'; + sampleValue = sampleNode; + reporter.test((Jasmine.pp(sampleValue) === "HTMLNode"), + "Expected Jasmine.pp(" + sampleValue + ") to return the string " + '"' + "HTMLNode" + '"' + " but got " + Jasmine.pp(sampleValue)); + + sampleValue = {foo: sampleNode}; + reporter.test((Jasmine.pp(sampleValue) === "{ foo : HTMLNode }"), + "Expected Jasmine.pp({ foo : " + sampleNode + " }) to return the string " + '"' + "{ foo: HTMLNode }" + '"' + " but got " + Jasmine.pp(sampleValue)); + + //todo object with function +}; var testMatchersReporting = function () { var results = []; var expected = new Jasmine.Matchers(true, results); - expected.should_equal(true); - expected.should_equal(false); + expected.toEqual(true); + expected.toEqual(false); reporter.test((results.length == 2), "Results array doesn't have 2 results"); @@ -93,18 +438,93 @@ var testMatchersReporting = function () { results = []; expected = new Jasmine.Matchers(false, results); - expected.should_equal(true); + expected.toEqual(true); + + var expectedMessage = 'Expected

true

but got

false
'; + reporter.test((results[0].message == expectedMessage), + "Failed expectation didn't test the failure message, expected: " + expectedMessage + " got: " + results[0].message); + + results = []; + expected = new Jasmine.Matchers(null, results); + expected.toEqual('not null'); + + expectedMessage = 'Expected

\'not null\'

but got

null
'; + reporter.test((results[0].message == expectedMessage), + "Failed expectation didn't test the failure message, expected: " + expectedMessage + " got: " + results[0].message); + + results = []; + expected = new Jasmine.Matchers(undefined, results); + expected.toEqual('not undefined'); + + expectedMessage = 'Expected

\'not undefined\'

but got

undefined
'; + reporter.test((results[0].message == expectedMessage), + "Failed expectation didn't test the failure message, expected: " + expectedMessage + " got: " + results[0].message); + + + results = []; + expected = new Jasmine.Matchers({foo:'one',baz:'two', more: 'blah'}, results); + expected.toEqual({foo:'one', bar: 'three &', baz: '2'}); + + expectedMessage = + "Expected

{ foo : 'one', bar : '<b>three</b> &', baz : '2' }

but got

{ foo : 'one', baz : 'two', more : 'blah' }
" + + "

Different Keys:
" + + "expected has key 'bar', but missing from actual.
" + + "expected missing key 'more', but present in actual.
" + + "

Different Values:
" + + "'bar' was

'<b>three</b> &'

in expected, but was

'undefined'

in actual.

" + + "'baz' was

'2'

in expected, but was

'two'

in actual.

"; + var actualMessage = results[0].message; + reporter.test((actualMessage == expectedMessage), + "Matcher message was incorrect. This is the message we expected:

" + expectedMessage + "

This is the message we got:

" + actualMessage); - reporter.test((results[0].message == 'Expected true but got false.'), - "Failed expectation didn't test the failure message"); results = []; expected = new Jasmine.Matchers(true, results); - expected.should_equal(true); + expected.toEqual(true); reporter.test((results[0].message == 'Passed.'), "Passing expectation didn't test the passing message"); -} +}; + +var testDisabledSpecs = function () { + var xitSpecWasRun = false; + var suite = describe('default current suite', function() { + + xit('disabled spec').runs(function () { + xitSpecWasRun = true; + }); + + it('enabled spec').runs(function () { + var foo = 'bar'; + expect(foo).toEqual('bar'); + }); + + }); + + + suite.execute(); + + reporter.test((suite.specs.length === 1), + "Only one spec should be defined in this suite."); + + reporter.test((xitSpecWasRun === false), + "xitSpec should not have been run."); + +}; + +var testDisabledSuites = function() { + var dontChangeMe = 'dontChangeMe'; + var disabledSuite = xdescribe('a disabled suite', function() { + it('enabled spec, but should not be run', function() { + dontChangeMe = 'changed'; + }); + }); + + disabledSuite.execute(); + + reporter.test((dontChangeMe === 'dontChangeMe'), + "spec in disabled suite should not have been run."); +}; var testSpecs = function () { var currentSuite = describe('default current suite', function() { @@ -116,7 +536,7 @@ var testSpecs = function () { var another_spec = it('spec with an expectation').runs(function () { var foo = 'bar'; - this.expects_that(foo).should_equal('bar'); + expect(foo).toEqual('bar'); }); another_spec.execute(); another_spec.done = true; @@ -130,7 +550,7 @@ var testSpecs = function () { var yet_another_spec = it('spec with failing expectation').runs(function () { var foo = 'bar'; - this.expects_that(foo).should_equal('baz'); + expect(foo).toEqual('baz'); }); yet_another_spec.execute(); yet_another_spec.done = true; @@ -142,30 +562,61 @@ var testSpecs = function () { var foo = 'bar'; var baz = 'quux'; - this.expects_that(foo).should_equal('bar'); - this.expects_that(baz).should_equal('quux'); + expect(foo).toEqual('bar'); + expect(baz).toEqual('quux'); }); yet_yet_another_spec.execute(); yet_yet_another_spec.done = true; reporter.test((yet_yet_another_spec.results.results.length === 2), "Spec doesn't support multiple expectations"); -} +}; + +var testSpecsWithoutRunsBlock = function () { + var currentSuite = describe('default current suite', function() { + }); + + var another_spec = it('spec with an expectation', function () { + var foo = 'bar'; + expect(foo).toEqual('bar'); + expect(foo).toEqual('baz'); + }); + + another_spec.execute(); + another_spec.done = true; + + reporter.test((another_spec.results.results.length === 2), + "Results length should be 2, got " + another_spec.results.results.length); + reporter.test((another_spec.results.results[0].passed === true), + "In a spec without a run block, expected first expectation result to be true but was false"); + reporter.test((another_spec.results.results[1].passed === false), + "In a spec without a run block, expected second expectation result to be false but was true"); + reporter.test((another_spec.results.description === 'spec with an expectation'), + "In a spec without a run block, results did not include the spec's description"); + +}; var testAsyncSpecs = function () { var foo = 0; + //set a bogus suite for the spec to attach to + Jasmine.getEnv().currentSuite = {specs: []}; + var a_spec = it('simple queue test', function () { runs(function () { foo++; }); runs(function () { - this.expects_that(foo).should_equal(1) + expect(foo).toEqual(1); }); }); - reporter.test(a_spec.queue.length === 2, - 'Spec queue length is not 2'); + reporter.test(a_spec.queue.length === 1, + 'Expected spec queue length to be 1, was ' + a_spec.queue.length); + + a_spec.execute(); + reporter.test(a_spec.queue.length === 3, + 'Expected spec queue length to be 3, was ' + a_spec.queue.length); foo = 0; a_spec = it('spec w/ queued statments', function () { @@ -173,7 +624,7 @@ var testAsyncSpecs = function () { foo++; }); runs(function () { - this.expects_that(foo).should_equal(1); + expect(foo).toEqual(1); }); }); @@ -188,27 +639,24 @@ var testAsyncSpecs = function () { a_spec = it('spec w/ queued statments', function () { runs(function () { setTimeout(function() { - foo++ + foo++; }, 500); }); waits(1000); runs(function() { - this.expects_that(foo).should_equal(1); + expect(foo).toEqual(1); }); }); - var mockSuite = { - next: function() { - reporter.test((a_spec.results.results.length === 1), - 'Calling waits(): Spec queue did not run all functions'); - - reporter.test((a_spec.results.results[0].passed === true), - 'Calling waits(): Queued expectation failed'); - } - }; - a_spec.execute(); - waitForDone(a_spec, mockSuite); + Clock.tick(500); + Clock.tick(500); + + reporter.test((a_spec.results.results.length === 1), + 'Calling waits(): Spec queue did not run all functions'); + + reporter.test((a_spec.results.results[0].passed === true), + 'Calling waits(): Queued expectation failed'); var bar = 0; var another_spec = it('spec w/ queued statments', function () { @@ -226,22 +674,24 @@ var testAsyncSpecs = function () { }); waits(500); runs(function () { - this.expects_that(bar).should_equal(2); + expect(bar).toEqual(2); }); }); - mockSuite = { - next: function() { - reporter.test((another_spec.queue.length === 3), - 'Calling 2 waits(): Spec queue was less than expected length'); - reporter.test((another_spec.results.results.length === 1), - 'Calling 2 waits(): Spec queue did not run all functions'); - reporter.test((another_spec.results.results[0].passed === true), - 'Calling 2 waits(): Queued expectation failed'); - } - }; + reporter.test((another_spec.queue.length === 1), + 'Calling 2 waits(): Expected queue length to be 1, got ' + another_spec.queue.length); + another_spec.execute(); - waitForDone(another_spec, mockSuite); + + Clock.tick(1000); + reporter.test((another_spec.queue.length === 4), + 'Calling 2 waits(): Expected queue length to be 4, got ' + another_spec.queue.length); + + reporter.test((another_spec.results.results.length === 1), + 'Calling 2 waits(): Spec queue did not run all functions'); + + reporter.test((another_spec.results.results[0].passed === true), + 'Calling 2 waits(): Queued expectation failed'); var baz = 0; var yet_another_spec = it('spec w/ async fail', function () { @@ -252,26 +702,21 @@ var testAsyncSpecs = function () { }); waits(100); runs(function() { - this.expects_that(baz).should_equal(1); + expect(baz).toEqual(1); }); }); - mockSuite = { - next: function() { - - reporter.test((yet_another_spec.queue.length === 2), - 'Calling 2 waits(): Spec queue was less than expected length'); - reporter.test((yet_another_spec.results.results.length === 1), - 'Calling 2 waits(): Spec queue did not run all functions'); - reporter.test((yet_another_spec.results.results[0].passed === false), - 'Calling 2 waits(): Queued expectation failed'); - } - }; - yet_another_spec.execute(); - waitForDone(yet_another_spec, mockSuite); -} + Clock.tick(250); + + reporter.test((yet_another_spec.queue.length === 3), + 'Calling 2 waits(): Expected queue length to be 3, got ' + another_spec.queue.length); + reporter.test((yet_another_spec.results.results.length === 1), + 'Calling 2 waits(): Spec queue did not run all functions'); + reporter.test((yet_another_spec.results.results[0].passed === false), + 'Calling 2 waits(): Queued expectation failed'); +}; var testAsyncSpecsWithMockSuite = function () { var bar = 0; @@ -287,34 +732,125 @@ var testAsyncSpecsWithMockSuite = function () { bar++; }, 250); }); - waits(1500) + waits(1500); runs(function() { - this.expects_that(bar).should_equal(2); + expect(bar).toEqual(2); }); }); - var mockSuite = { - next: function () { - reporter.test((another_spec.queue.length === 3), - 'Calling 2 waits(): Spec queue was less than expected length'); - reporter.test((another_spec.results.results.length === 1), - 'Calling 2 waits(): Spec queue did not run all functions'); - reporter.test((another_spec.results.results[0].passed === true), - 'Calling 2 waits(): Queued expectation failed'); - } - }; another_spec.execute(); - waitForDone(another_spec, mockSuite); -} + Clock.tick(2000); + reporter.test((another_spec.queue.length === 4), + 'Calling 2 waits(): Expected queue length to be 4, got ' + another_spec.queue.length); + reporter.test((another_spec.results.results.length === 1), + 'Calling 2 waits(): Spec queue did not run all functions'); + reporter.test((another_spec.results.results[0].passed === true), + 'Calling 2 waits(): Queued expectation failed'); +}; -var waitForDone = function(spec, mockSuite) { - var id = setInterval(function () { - if (spec.finished) { - clearInterval(id); - mockSuite.next(); - } - }, 150); -} +var testWaitsFor = function() { + var doneWaiting = false; + var runsBlockExecuted = false; + + var spec; + describe('foo', function() { + spec = it('has a waits for', function() { + runs(function() { + }); + + waitsFor(500, function() { + return doneWaiting; + }); + + runs(function() { + runsBlockExecuted = true; + }); + }); + }); + + spec.execute(); + reporter.test(runsBlockExecuted === false, 'should not have executed runs block yet'); + Clock.tick(100); + doneWaiting = true; + Clock.tick(100); + reporter.test(runsBlockExecuted === true, 'should have executed runs block'); +}; + +var testWaitsForFailsWithMessage = function() { + var spec; + describe('foo', function() { + spec = it('has a waits for', function() { + runs(function() { + }); + + waitsFor(500, function() { + return false; // force a timeout + }, 'my awesome condition'); + + runs(function() { + }); + }); + }); + + spec.execute(); + Clock.tick(1000); + var actual = spec.results.results[0].message; + var expected = 'timeout: timed out after 500 msec waiting for my awesome condition'; + reporter.test(actual === expected, + 'expected "' + expected + '" but found "' + actual + '"'); +}; + +var testWaitsForFailsIfTimeout = function() { + var runsBlockExecuted = false; + + var spec; + describe('foo', function() { + spec = it('has a waits for', function() { + runs(function() { + }); + + waitsFor(500, function() { + return false; // force a timeout + }); + + runs(function() { + runsBlockExecuted = true; + }); + }); + }); + + spec.execute(); + reporter.test(runsBlockExecuted === false, 'should not have executed runs block yet'); + Clock.tick(100); + reporter.test(runsBlockExecuted === false, 'should not have executed runs block yet'); + Clock.tick(400); + reporter.test(runsBlockExecuted === false, 'should have timed out, so the second runs block should not have been called'); + var actual = spec.results.results[0].message; + var expected = 'timeout: timed out after 500 msec waiting for something to happen'; + reporter.test(actual === expected, + 'expected "' + expected + '" but found "' + actual + '"'); +}; + +var testSpecAfter = function() { + var log = ""; + var spec; + var suite = describe("has after", function() { + spec = it('spec with after', function() { + runs(function() { + log += "spec"; + }); + }); + }); + spec.after(function() { + log += "after1"; + }); + spec.after(function() { + log += "after2"; + }); + + suite.execute(); + reporter.test((log == "specafter1after2"), "after function should be executed after spec runs"); +}; var testSuites = function () { @@ -361,8 +897,13 @@ var testSuites = function () { }); }); - reporter.test((suite.specs[0].queue.length === 2), - "Suite's spec did not get 2 functions pushed"); + reporter.test((suite.specs[0].queue.length === 1), + "Suite's spec length should have been 1, was " + suite.specs[0].queue.length); + + suite.execute(); + + reporter.test((suite.specs[0].queue.length === 3), + "Suite's spec length should have been 3, was " + suite.specs[0].queue.length); var foo = 0; suite = describe('one suite description', function () { @@ -381,13 +922,11 @@ var testSuites = function () { suite.execute(); - setTimeout(function () { - reporter.test((suite.specs.length === 2), - "Suite doesn't have two specs"); - reporter.test((foo === 2), - "Suite didn't execute both specs"); - }, 500); -} + reporter.test((suite.specs.length === 2), + "Suite doesn't have two specs"); + reporter.test((foo === 2), + "Suite didn't execute both specs"); +}; var testBeforeAndAfterCallbacks = function () { @@ -400,70 +939,370 @@ var testBeforeAndAfterCallbacks = function () { it('should be a spec', function () { runs(function() { this.foo++; - this.expects_that(this.foo).should_equal(2); + expect(this.foo).toEqual(2); }); }); it('should be another spec', function () { runs(function() { this.foo++; - this.expects_that(this.foo).should_equal(2); + expect(this.foo).toEqual(2); }); }); }); suiteWithBefore.execute(); - setTimeout(function () { - var suite = suiteWithBefore; - reporter.test((suite.beforeEach !== undefined), - "Suite's beforeEach was not defined"); - reporter.test((suite.results.results[0].results[0].passed === true), - "the first spec's foo should have been 2"); - reporter.test((suite.results.results[1].results[0].passed === true), - "the second spec's this.foo should have been 2"); - }, 750); + var suite = suiteWithBefore; + reporter.test((suite.beforeEach !== undefined), + "testBeforeAndAfterCallbacks: Suite's beforeEach was not defined"); + reporter.test((suite.results.results[0].results[0].passed === true), + "testBeforeAndAfterCallbacks: the first spec's foo should have been 2"); + reporter.test((suite.results.results[1].results[0].passed === true), + "testBeforeAndAfterCallbacks: the second spec's this.foo should have been 2"); - setTimeout(function () { - var suiteWithAfter = describe('one suite with an after_each', function () { + var suiteWithAfter = describe('one suite with an after_each', function () { - it('should be a spec with an after_each', function () { - runs(function() { - this.foo = 0; - this.foo++; - this.expects_that(this.foo).should_equal(1); - }); - }); - - it('should be another spec with an after_each', function () { - runs(function() { - this.foo = 0; - this.foo++; - this.expects_that(this.foo).should_equal(1); - }); - }); - - afterEach(function () { + it('should be a spec with an after_each', function () { + runs(function() { this.foo = 0; + this.foo++; + expect(this.foo).toEqual(1); }); }); - suiteWithAfter.execute(); - setTimeout(function () { - var suite = suiteWithAfter; - reporter.test((suite.afterEach !== undefined), - "Suite's afterEach was not defined"); - reporter.test((suite.results.results[0].results[0].passed === true), - "afterEach failure: " + suite.results.results[0].results[0].message); - reporter.test((suite.specs[0].foo === 0), - "afterEach failure: foo was not reset to 0"); - reporter.test((suite.results.results[1].results[0].passed === true), - "afterEach failure: " + suite.results.results[0].results[0].message); - reporter.test((suite.specs[1].foo === 0), - "afterEach failure: foo was not reset to 0"); - }, 500); - }, 1200); + it('should be another spec with an after_each', function () { + runs(function() { + this.foo = 0; + this.foo++; + expect(this.foo).toEqual(1); + }); + }); -} + afterEach(function () { + this.foo = 0; + }); + }); + + suiteWithAfter.execute(); + var suite = suiteWithAfter; + reporter.test((suite.afterEach !== undefined), + "testBeforeAndAfterCallbacks: Suite's afterEach was not defined"); + reporter.test((suite.results.results[0].results[0].passed === true), + "testBeforeAndAfterCallbacks: afterEach failure: " + suite.results.results[0].results[0].message); + reporter.test((suite.specs[0].foo === 0), + "testBeforeAndAfterCallbacks: afterEach failure: foo was not reset to 0"); + reporter.test((suite.results.results[1].results[0].passed === true), + "testBeforeAndAfterCallbacks: afterEach failure: " + suite.results.results[0].results[0].message); + reporter.test((suite.specs[1].foo === 0), + "testBeforeAndAfterCallbacks: afterEach failure: foo was not reset to 0"); + +}; + +var testBeforeExecutesSafely = function() { + var report = ""; + var suite = describe('before fails on first test, passes on second', function() { + var counter = 0; + beforeEach(function() { + counter++; + if (counter == 1) { + throw "before failure"; + } + }); + it("first should not run because before fails", function() { + runs(function() { + report += "first"; + expect(true).toEqual(true); + }); + }); + it("second should run and pass because before passes", function() { + runs(function() { + report += "second"; + expect(true).toEqual(true); + }); + }); + }); + + suite.execute(); + + reporter.test((report === "firstsecond"), "both tests should run"); + reporter.test(suite.specs[0].results.results[0].passed === false, "1st spec should fail"); + reporter.test(suite.specs[1].results.results[0].passed === true, "2nd spec should pass"); + + reporter.test(suite.specResults[0].results[0].passed === false, "1st spec should fail"); + reporter.test(suite.specResults[1].results[0].passed === true, "2nd spec should pass"); +}; + +var testAfterExecutesSafely = function() { + var report = ""; + var suite = describe('after fails on first test, then passes', function() { + var counter = 0; + afterEach(function() { + counter++; + if (counter == 1) { + throw "after failure"; + } + }); + it("first should run, expectation passes, but spec fails because after fails", function() { + runs(function() { + report += "first"; + expect(true).toEqual(true); + }); + }); + it("second should run and pass because after passes", function() { + runs(function() { + report += "second"; + expect(true).toEqual(true); + }); + }); + it("third should run and pass because after passes", function() { + runs(function() { + report += "third"; + expect(true).toEqual(true); + }); + }); + }); + + suite.execute(); + + reporter.test((report === "firstsecondthird"), "all tests should run"); + //After each errors should not go in spec results because it confuses the count. + reporter.test(suite.specs.length === 3, 'testAfterExecutesSafely should have results for three specs'); + reporter.test(suite.specs[0].results.results[0].passed === true, "testAfterExecutesSafely 1st spec should pass"); + reporter.test(suite.specs[1].results.results[0].passed === true, "testAfterExecutesSafely 2nd spec should pass"); + reporter.test(suite.specs[2].results.results[0].passed === true, "testAfterExecutesSafely 3rd spec should pass"); + + reporter.test(suite.specResults[0].results[0].passed === true, "testAfterExecutesSafely 1st result for 1st suite spec should pass"); + reporter.test(suite.specResults[0].results[1].passed === false, "testAfterExecutesSafely 2nd result for 1st suite spec should fail because afterEach failed"); + reporter.test(suite.specResults[1].results[0].passed === true, "testAfterExecutesSafely 2nd suite spec should pass"); + reporter.test(suite.specResults[2].results[0].passed === true, "testAfterExecutesSafely 3rd suite spec should pass"); + +}; + + +var testSpecSpy = function () { + var suite = describe('Spec spying', function () { + it('should replace the specified function with a spy object', function() { + var originalFunctionWasCalled = false; + var TestClass = { + someFunction: function() { + originalFunctionWasCalled = true; + } + }; + this.spyOn(TestClass, 'someFunction'); + + expect(TestClass.someFunction.wasCalled).toEqual(false); + expect(TestClass.someFunction.callCount).toEqual(0); + TestClass.someFunction('foo'); + expect(TestClass.someFunction.wasCalled).toEqual(true); + expect(TestClass.someFunction.callCount).toEqual(1); + expect(TestClass.someFunction.mostRecentCall.args).toEqual(['foo']); + expect(TestClass.someFunction.mostRecentCall.object).toEqual(TestClass); + expect(originalFunctionWasCalled).toEqual(false); + + TestClass.someFunction('bar'); + expect(TestClass.someFunction.callCount).toEqual(2); + expect(TestClass.someFunction.mostRecentCall.args).toEqual(['bar']); + }); + + it('should return allow you to view args for a particular call', function() { + var originalFunctionWasCalled = false; + var TestClass = { + someFunction: function() { + originalFunctionWasCalled = true; + } + }; + this.spyOn(TestClass, 'someFunction'); + + TestClass.someFunction('foo'); + TestClass.someFunction('bar'); + expect(TestClass.someFunction.argsForCall[0]).toEqual(['foo']); + expect(TestClass.someFunction.argsForCall[1]).toEqual(['bar']); + expect(TestClass.someFunction.mostRecentCall.args).toEqual(['bar']); + }); + + it('should be possible to call through to the original method, or return a specific result', function() { + var originalFunctionWasCalled = false; + var passedArgs; + var passedObj; + var TestClass = { + someFunction: function() { + originalFunctionWasCalled = true; + passedArgs = arguments; + passedObj = this; + return "return value from original function"; + } + }; + + this.spyOn(TestClass, 'someFunction').andCallThrough(); + var result = TestClass.someFunction('arg1', 'arg2'); + expect(result).toEqual("return value from original function"); + expect(originalFunctionWasCalled).toEqual(true); + expect(passedArgs).toEqual(['arg1', 'arg2']); + expect(passedObj).toEqual(TestClass); + expect(TestClass.someFunction.wasCalled).toEqual(true); + }); + + it('should be possible to return a specific value', function() { + var originalFunctionWasCalled = false; + var TestClass = { + someFunction: function() { + originalFunctionWasCalled = true; + return "return value from original function"; + } + }; + + this.spyOn(TestClass, 'someFunction').andReturn("some value"); + originalFunctionWasCalled = false; + var result = TestClass.someFunction('arg1', 'arg2'); + expect(result).toEqual("some value"); + expect(originalFunctionWasCalled).toEqual(false); + }); + + it('should be possible to throw a specific error', function() { + var originalFunctionWasCalled = false; + var TestClass = { + someFunction: function() { + originalFunctionWasCalled = true; + return "return value from original function"; + } + }; + + this.spyOn(TestClass, 'someFunction').andThrow(new Error('fake error')); + var exception; + try { + TestClass.someFunction('arg1', 'arg2'); + } catch (e) { + exception = e; + } + expect(exception.message).toEqual('fake error'); + expect(originalFunctionWasCalled).toEqual(false); + }); + + it('should be possible to call a specified function', function() { + var originalFunctionWasCalled = false; + var fakeFunctionWasCalled = false; + var passedArgs; + var passedObj; + var TestClass = { + someFunction: function() { + originalFunctionWasCalled = true; + return "return value from original function"; + } + }; + + this.spyOn(TestClass, 'someFunction').andCallFake(function() { + fakeFunctionWasCalled = true; + passedArgs = arguments; + passedObj = this; + return "return value from fake function"; + }); + + var result = TestClass.someFunction('arg1', 'arg2'); + expect(result).toEqual("return value from fake function"); + expect(originalFunctionWasCalled).toEqual(false); + expect(fakeFunctionWasCalled).toEqual(true); + expect(passedArgs).toEqual(['arg1', 'arg2']); + expect(passedObj).toEqual(TestClass); + expect(TestClass.someFunction.wasCalled).toEqual(true); + }); + + it('is torn down when Pockets.removeAllSpies is called', function() { + var originalFunctionWasCalled = false; + var TestClass = { + someFunction: function() { + originalFunctionWasCalled = true; + } + }; + this.spyOn(TestClass, 'someFunction'); + + TestClass.someFunction('foo'); + expect(originalFunctionWasCalled).toEqual(false); + + this.removeAllSpies(); + + TestClass.someFunction('foo'); + expect(originalFunctionWasCalled).toEqual(true); + }); + + it('adds removeAllSpies to the after spec teardown', function() { + var originalFunctionWasCalled = false; + var TestClass = { + someFunction: function() { + originalFunctionWasCalled = true; + } + }; + + expect(this.afterCallbacks.length).toEqual(0); + + this.spyOn(TestClass, 'someFunction'); + + expect(this.afterCallbacks.length).toEqual(1); + this.afterCallbacks[0].call(); + + TestClass.someFunction('foo'); + expect(originalFunctionWasCalled).toEqual(true); + }); + + it('throws an exception when some method is spied on twice', function() { + var TestClass = { someFunction: function() { + } }; + this.spyOn(TestClass, 'someFunction'); + var exception; + try { + this.spyOn(TestClass, 'someFunction'); + } catch (e) { + exception = e; + } + expect(exception).toBeDefined(); + }); + + it('should be able to reset a spy', function() { + var TestClass = { someFunction: function() {} }; + this.spyOn(TestClass, 'someFunction'); + + expect(TestClass.someFunction).wasNotCalled(); + TestClass.someFunction(); + expect(TestClass.someFunction).wasCalled(); + TestClass.someFunction.reset(); + expect(TestClass.someFunction).wasNotCalled(); + expect(TestClass.someFunction.callCount).toEqual(0); + }); + }); + + suite.execute(); + + for (var j = 0; j < suite.specResults.length; j++) { + reporter.test(suite.specResults[j].results.length > 0, "testSpecSpy: should have results, got " + suite.specResults[j].results.length); + for (var i = 0; i < suite.specResults[j].results.length; i++) { + reporter.test(suite.specResults[j].results[i].passed === true, "testSpecSpy: expectation number " + i + " failed: " + suite.specResults[j].results[i].message); + } + } +}; + +var testExplodes = function () { + var suite = describe('exploding', function () { + it('should throw an exception when this.explodes is called inside a spec', function() { + var exceptionMessage = false; + + try { + this.explodes(); + } + catch (e) { + exceptionMessage = e; + } + expect(exceptionMessage).toEqual('explodes function should not have been called'); + }); + + }); + suite.execute(); + + for (var j = 0; j < suite.specResults.length; j++) { + reporter.test(suite.specResults[j].results.length > 0, "testExplodes: should have results, got " + suite.specResults[j].results.length); + for (var i = 0; i < suite.specResults[j].results.length; i++) { + reporter.test(suite.specResults[j].results[i].passed === true, "testExplodes: expectation number " + i + " failed: " + suite.specResults[j].results[i].message); + } + } +}; var testSpecScope = function () { @@ -482,33 +1321,31 @@ var testSpecScope = function () { }); runs(function() { - this.expects_that(this.foo).should_equal(2); + expect(this.foo).toEqual(2); }); waits(300); runs(function() { - this.expects_that(this.foo).should_equal(2); + expect(this.foo).toEqual(2); }); }); }); suite.execute(); - - setTimeout(function () { - reporter.test((suite.specs[0].foo === 2), - "Spec does not maintain scope in between functions"); - reporter.test((suite.specs[0].results.results.length === 2), - "Spec did not get results for all expectations"); - reporter.test((suite.specs[0].results.results[0].passed === false), - "Spec did not return false for a failed expectation"); - reporter.test((suite.specs[0].results.results[1].passed === true), - "Spec did not return true for a passing expectation"); - reporter.test((suite.results.description === 'one suite description'), - "Suite did not get its description in the results"); - }, 1000); -} + Clock.tick(600); + reporter.test((suite.specs[0].foo === 2), + "Spec does not maintain scope in between functions"); + reporter.test((suite.specs[0].results.results.length === 2), + "Spec did not get results for all expectations"); + reporter.test((suite.specs[0].results.results[0].passed === false), + "Spec did not return false for a failed expectation"); + reporter.test((suite.specs[0].results.results[1].passed === true), + "Spec did not return true for a passing expectation"); + reporter.test((suite.results.description === 'one suite description'), + "Suite did not get its description in the results"); +}; var testRunner = function() { @@ -534,7 +1371,7 @@ var testRunner = function() { describe('one suite description', function () { it('should be a test', function() { runs(function () { - this.expects_that(true).should_equal(true); + expect(true).toEqual(true); }); }); }); @@ -542,22 +1379,46 @@ var testRunner = function() { describe('another suite description', function () { it('should be another test', function() { runs(function () { - this.expects_that(true).should_equal(false); + expect(true).toEqual(false); }); }); }); runner.execute(); - setTimeout(function () { - reporter.test((runner.suites.length === 2), - "Runner expected two suites, got " + runner.suites.length); - reporter.test((runner.suites[0].specs[0].results.results[0].passed === true), - "Runner should have run specs in first suite"); - reporter.test((runner.suites[1].specs[0].results.results[0].passed === false), - "Runner should have run specs in second suite"); - }, 1000); -} + reporter.test((runner.suites.length === 2), + "Runner expected two suites, got " + runner.suites.length); + reporter.test((runner.suites[0].specs[0].results.results[0].passed === true), + "Runner should have run specs in first suite"); + reporter.test((runner.suites[1].specs[0].results.results[0].passed === false), + "Runner should have run specs in second suite"); + + runner = Runner(); + xdescribe('one suite description', function () { + it('should be a test', function() { + runs(function () { + expect(true).toEqual(true); + }); + }); + }); + + describe('another suite description', function () { + it('should be another test', function() { + runs(function () { + expect(true).toEqual(false); + }); + }); + }); + + runner.execute(); + + reporter.test((runner.suites.length === 1), + "Runner expected 1 suite, got " + runner.suites.length); + reporter.test((runner.suites[0].specs[0].results.results[0].passed === false), + "Runner should have run specs in first suite"); + reporter.test((runner.suites[1] === undefined), + "Second suite should be undefined, but was " + reporter.toJSON(runner.suites[1])); +}; var testRunnerFinishCallback = function () { var runner = Runner(); @@ -570,7 +1431,7 @@ var testRunnerFinishCallback = function () { runner.finishCallback = function () { foo++; - } + }; runner.finish(); @@ -578,13 +1439,13 @@ var testRunnerFinishCallback = function () { "Runner finished flag was not set."); reporter.test((foo === 1), "Runner finish callback was not called"); -} +}; var testNestedResults = function () { // Leaf case - var results = nestedResults(); + var results = new Jasmine.NestedResults(); results.push({passed: true, message: 'Passed.'}); @@ -609,15 +1470,15 @@ var testNestedResults = function () { "nestedResults.push didn't increment failedCount"); // Branch case - var leafResultsOne = nestedResults(); + var leafResultsOne = new Jasmine.NestedResults(); leafResultsOne.push({passed: true, message: ''}); leafResultsOne.push({passed: false, message: ''}); - var leafResultsTwo = nestedResults(); + var leafResultsTwo = new Jasmine.NestedResults(); leafResultsTwo.push({passed: true, message: ''}); leafResultsTwo.push({passed: false, message: ''}); - var branchResults = nestedResults(); + var branchResults = new Jasmine.NestedResults(); branchResults.push(leafResultsOne); branchResults.push(leafResultsTwo); @@ -629,14 +1490,14 @@ var testNestedResults = function () { "Branch Results should have 2 passed, has " + branchResults.passedCount); reporter.test((branchResults.failedCount === 2), "Branch Results should have 2 failed, has " + branchResults.failedCount); -} +}; var testResults = function () { var runner = Runner(); describe('one suite description', function () { it('should be a test', function() { runs(function () { - this.expects_that(true).should_equal(true); + expect(true).toEqual(true); }); }); }); @@ -644,46 +1505,44 @@ var testResults = function () { describe('another suite description', function () { it('should be another test', function() { runs(function () { - this.expects_that(true).should_equal(false); + expect(true).toEqual(false); }); }); }); runner.execute(); - setTimeout(function () { - reporter.test((runner.results.totalCount === 2), - 'Expectation count should be 2, but was ' + runner.results.totalCount); - reporter.test((runner.results.passedCount === 1), - 'Expectation Passed count should be 1, but was ' + runner.results.passedCount); - reporter.test((runner.results.failedCount === 1), - 'Expectation Failed count should be 1, but was ' + runner.results.failedCount); - reporter.test((runner.results.description === 'All Jasmine Suites'), - 'Jasmine Runner does not have the expected description, has: ' + runner.results.description); - }, 500); + reporter.test((runner.results.totalCount === 2), + 'Expectation count should be 2, but was ' + runner.results.totalCount); + reporter.test((runner.results.passedCount === 1), + 'Expectation Passed count should be 1, but was ' + runner.results.passedCount); + reporter.test((runner.results.failedCount === 1), + 'Expectation Failed count should be 1, but was ' + runner.results.failedCount); + reporter.test((runner.results.description === 'All Jasmine Suites'), + 'Jasmine Runner does not have the expected description, has: ' + runner.results.description); -} +}; var testReporterWithCallbacks = function () { - jasmine = Jasmine.init(); + Jasmine.currentEnv_ = new Jasmine.Env(); var runner = Runner(); describe('Suite for JSON Reporter with Callbacks', function () { it('should be a test', function() { runs(function () { - this.expects_that(true).should_equal(true); + expect(true).toEqual(true); }); }); it('should be a failing test', function() { runs(function () { - this.expects_that(false).should_equal(true); + expect(false).toEqual(true); }); }); }); describe('Suite for JSON Reporter with Callbacks 2', function () { it('should be a test', function() { runs(function () { - this.expects_that(true).should_equal(true); + expect(true).toEqual(true); }); }); @@ -695,97 +1554,89 @@ var testReporterWithCallbacks = function () { var specCallback = function (results) { foo++; - } + }; var suiteCallback = function (results) { bar++; - } + }; var runnerCallback = function (results) { baz++; - } + }; - jasmine.reporter = Jasmine.Reporters.reporter({ + Jasmine.getEnv().reporter = Jasmine.Reporters.reporter({ specCallback: specCallback, suiteCallback: suiteCallback, runnerCallback: runnerCallback }); runner.execute(); - setTimeout(function() { - reporter.test((foo === 3), - 'foo was expected to be 3, was ' + foo); - reporter.test((bar === 2), - 'bar was expected to be 2, was ' + bar); - reporter.test((baz === 1), - 'baz was expected to be 1, was ' + baz); - - }, 750); -} + reporter.test((foo === 3), + 'foo was expected to be 3, was ' + foo); + reporter.test((bar === 2), + 'bar was expected to be 2, was ' + bar); + reporter.test((baz === 1), + 'baz was expected to be 1, was ' + baz); +}; var testJSONReporter = function () { - jasmine = Jasmine.init(); + Jasmine.currentEnv_ = new Jasmine.Env(); var runner = Runner(); describe('Suite for JSON Reporter, NO DOM', function () { it('should be a test', function() { runs(function () { - this.expects_that(true).should_equal(true); + expect(true).toEqual(true); }); }); }); - jasmine.reporter = Jasmine.Reporters.JSON(); + Jasmine.getEnv().reporter = Jasmine.Reporters.JSON(); runner.execute(); - setTimeout(function() { - var expectedSpecJSON = '{"totalCount": 1, "passedCount": 1, "failedCount": 0, "results": [{"passed": true, "message": "Passed."}], "description": "should be a test"}'; - var expectedSuiteJSON = '{"totalCount": 1, "passedCount": 1, "failedCount": 0, "results": [{"totalCount": 1, "passedCount": 1, "failedCount": 0, "results": [{"passed": true, "message": "Passed."}], "description": "should be a test"}], "description": "Suite for JSON Reporter, NO DOM"}'; - var expectedRunnerJSON = '{"totalCount": 1, "passedCount": 1, "failedCount": 0, "results": [{"totalCount": 1, "passedCount": 1, "failedCount": 0, "results": [{"totalCount": 1, "passedCount": 1, "failedCount": 0, "results": [{"passed": true, "message": "Passed."}], "description": "should be a test"}], "description": "Suite for JSON Reporter, NO DOM"}], "description": "All Jasmine Suites"}'; + var expectedSpecJSON = '{"totalCount":1,"passedCount":1,"failedCount":0,"results":[{"passed":true,"message":"Passed."}],"description":"should be a test"}'; + var expectedSuiteJSON = '{"totalCount":1,"passedCount":1,"failedCount":0,"results":[' + expectedSpecJSON + '],"description":"Suite for JSON Reporter, NO DOM"}'; + var expectedRunnerJSON = '{"totalCount":1,"passedCount":1,"failedCount":0,"results":[' + expectedSuiteJSON + '],"description":"All Jasmine Suites"}'; - specJSON = jasmine.reporter.specJSON; - reporter.test((specJSON === expectedSpecJSON), - 'JSON Reporter does not have the expected Spec results report.
Expected:
' + expectedSpecJSON + - '
Got:
' + specJSON); + var specJSON = Jasmine.getEnv().reporter.specJSON; + reporter.test((specJSON === expectedSpecJSON), + 'JSON Reporter does not have the expected Spec results report.
Expected:
' + expectedSpecJSON + + '
Got:
' + specJSON); - suiteJSON = jasmine.reporter.suiteJSON; - reporter.test((suiteJSON === expectedSuiteJSON), - 'JSON Reporter does not have the expected Suite results report.
Expected:
' + expectedSuiteJSON + - '
Got:
' + suiteJSON); + var suiteJSON = Jasmine.getEnv().reporter.suiteJSON; + reporter.test((suiteJSON === expectedSuiteJSON), + 'JSON Reporter does not have the expected Suite results report.
Expected:
' + expectedSuiteJSON + + '
Got:
' + suiteJSON); - runnerJSON = jasmine.reporter.runnerJSON; - reporter.test((runnerJSON === expectedRunnerJSON), - 'JSON Reporter does not have the expected Runner results report.
Expected:
' + expectedRunnerJSON + - '
Got:
' + runnerJSON); - }, 500); -} + var runnerJSON = Jasmine.getEnv().reporter.runnerJSON; + reporter.test((runnerJSON === expectedRunnerJSON), + 'JSON Reporter does not have the expected Runner results report.
Expected:
' + expectedRunnerJSON + + '
Got:
' + runnerJSON); +}; var testJSONReporterWithDOM = function () { - jasmine = Jasmine.init(); + Jasmine.currentEnv_ = new Jasmine.Env(); var runner = Runner(); describe('Suite for JSON Reporter/DOM', function () { it('should be a test', function() { runs(function () { - this.expects_that(true).should_equal(true); + expect(true).toEqual(true); }); }); }); - jasmine.reporter = Jasmine.Reporters.JSONtoDOM('json_reporter_results'); + Jasmine.getEnv().reporter = Jasmine.Reporters.JSONtoDOM('json_reporter_results'); runner.execute(); - setTimeout(function() { - var expectedJSONString = '{"totalCount": 1, "passedCount": 1, "failedCount": 0, "results": [{"totalCount": 1, "passedCount": 1, "failedCount": 0, "results": [{"totalCount": 1, "passedCount": 1, "failedCount": 0, "results": [{"passed": true, "message": "Passed."}], "description": "should be a test"}], "description": "Suite for JSON Reporter/DOM"}], "description": "All Jasmine Suites"}'; - - reporter.test((document.getElementById('json_reporter_results').innerHTML === expectedJSONString), - 'JSON Reporter with DOM did not write the expected report to the DOM, got:' + document.getElementById('json_reporter_results').innerHTML); - }, 250); -} + var expectedJSONString = '{"totalCount":1,"passedCount":1,"failedCount":0,"results":[{"totalCount":1,"passedCount":1,"failedCount":0,"results":[{"totalCount":1,"passedCount":1,"failedCount":0,"results":[{"passed":true,"message":"Passed."}],"description":"should be a test"}],"description":"Suite for JSON Reporter/DOM"}],"description":"All Jasmine Suites"}'; + //innerText not supported in firefox. + reporter.test((document.getElementById('json_reporter_results').innerHTML == expectedJSONString), + 'JSON Reporter with DOM did not write the expected report to the DOM, got:' + document.getElementById('json_reporter_results').innerHTML); +}; var testHandlesBlankSpecs = function () { - jasmine = Jasmine.init(); + Jasmine.currentEnv_ = new Jasmine.Env(); var runner = Runner(); - describe('Suite for handles blank specs', function () { it('should be a test with a blank runs block', function() { runs(function () { @@ -797,13 +1648,11 @@ var testHandlesBlankSpecs = function () { }); runner.execute(); - setTimeout(function() { - reporter.test((runner.suites[0].specResults.length === 2), - 'Should have found 2 spec results, got ' + runner.suites[0].specResults.length); - reporter.test((runner.suites[0].results.passedCount === 2), - 'Should have found 2 passing specs, got ' + runner.suites[0].results.passedCount); - }, 250); -} + reporter.test((runner.suites[0].specResults.length === 2), + 'Should have found 2 spec results, got ' + runner.suites[0].specResults.length); + reporter.test((runner.suites[0].results.passedCount === 2), + 'Should have found 2 passing specs, got ' + runner.suites[0].results.passedCount); +}; var testFormatsExceptionMessages = function () { @@ -812,28 +1661,28 @@ var testFormatsExceptionMessages = function () { line: '1978', message: 'you got your foo in my bar', name: 'A Classic Mistake' - } + }; var sampleWebkitException = { sourceURL: 'foo.js', lineNumber: '1978', message: 'you got your foo in my bar', name: 'A Classic Mistake' - } + }; - var expected = 'A Classic Mistake: you got your foo in my bar in foo.js (line 1978)' + var expected = 'A Classic Mistake: you got your foo in my bar in foo.js (line 1978)'; - reporter.test((Jasmine.Util.formatException(sampleFirefoxException) === expected), - 'Should have got ' + expected + ' but got: ' + Jasmine.Util.formatException(sampleFirefoxException)); + reporter.test((Jasmine.util.formatException(sampleFirefoxException) === expected), + 'Should have got ' + expected + ' but got: ' + Jasmine.util.formatException(sampleFirefoxException)); - reporter.test((Jasmine.Util.formatException(sampleWebkitException) === expected), - 'Should have got ' + expected + ' but got: ' + Jasmine.Util.formatException(sampleWebkitException)); + reporter.test((Jasmine.util.formatException(sampleWebkitException) === expected), + 'Should have got ' + expected + ' but got: ' + Jasmine.util.formatException(sampleWebkitException)); }; var testHandlesExceptions = function () { - jasmine = Jasmine.init(); + Jasmine.currentEnv_ = new Jasmine.Env(); var runner = Runner(); - + //we run two exception tests to make sure we continue after throwing an exception describe('Suite for handles exceptions', function () { it('should be a test that fails because it throws an exception', function() { @@ -847,14 +1696,14 @@ var testHandlesExceptions = function () { fakeObject2.fakeMethod2(); }); runs(function () { - this.expects_that(true).should_equal(true); + expect(true).toEqual(true); }); }); it('should be a passing test that runs after exceptions are thrown', function() { runs(function () { - this.expects_that(true).should_equal(true); + expect(true).toEqual(true); }); }); @@ -870,54 +1719,53 @@ var testHandlesExceptions = function () { it('should be a passing test that runs after exceptions are thrown from a async test', function() { runs(function () { - this.expects_that(true).should_equal(true); + expect(true).toEqual(true); }); }); }); runner.execute(); + Clock.tick(400); //TODO: setting this to a large number causes failures, but shouldn't - setTimeout(function() { - reporter.test((runner.suites[0].specResults.length === 5), - 'Should have found 4 spec results, got ' + runner.suites[0].specResults.length); + reporter.test((runner.suites[0].specResults.length === 5), + 'Should have found 5 spec results, got ' + runner.suites[0].specResults.length); - reporter.test((runner.suites[0].specs[0].expectationResults[0].passed === false), - 'First test should have failed, got passed'); + reporter.test((runner.suites[0].specs[0].expectationResults[0].passed === false), + 'First test should have failed, got passed'); - reporter.test((typeof runner.suites[0].specs[0].expectationResults[0].message.search(/fakeObject/) !== -1), - 'First test should have contained /fakeObject/, got ' + runner.suites[0].specs[0].expectationResults[0].message); + reporter.test((typeof runner.suites[0].specs[0].expectationResults[0].message.search(/fakeObject/) !== -1), + 'First test should have contained /fakeObject/, got ' + runner.suites[0].specs[0].expectationResults[0].message); - reporter.test((runner.suites[0].specs[1].expectationResults[0].passed === false), - 'Second test should have a failing first result, got passed'); + reporter.test((runner.suites[0].specs[1].expectationResults[0].passed === false), + 'Second test should have a failing first result, got passed'); - reporter.test((typeof runner.suites[0].specs[1].expectationResults[0].message.search(/fakeObject2/) !== -1), - 'Second test should have contained /fakeObject2/, got ' + runner.suites[0].specs[1].expectationResults[0].message); + reporter.test((typeof runner.suites[0].specs[1].expectationResults[0].message.search(/fakeObject2/) !== -1), + 'Second test should have contained /fakeObject2/, got ' + runner.suites[0].specs[1].expectationResults[0].message); - reporter.test((runner.suites[0].specs[1].expectationResults[1].passed === true), - 'Second expectation in second test should have still passed'); + reporter.test((runner.suites[0].specs[1].expectationResults[1].passed === true), + 'Second expectation in second test should have still passed'); - reporter.test((runner.suites[0].specs[2].expectationResults[0].passed === true), - 'Third test should have passed, got failed'); + reporter.test((runner.suites[0].specs[2].expectationResults[0].passed === true), + 'Third test should have passed, got failed'); - reporter.test((runner.suites[0].specs[3].expectationResults[0].passed === false), - 'Fourth test should have a failing first result, got passed'); + reporter.test((runner.suites[0].specs[3].expectationResults[0].passed === false), + 'Fourth test should have a failing first result, got passed'); - reporter.test((typeof runner.suites[0].specs[3].expectationResults[0].message.search(/fakeObject3/) !== -1), - 'Fourth test should have contained /fakeObject3/, got ' + runner.suites[0].specs[3].expectationResults[0].message); - }, 2000); -} + reporter.test((typeof runner.suites[0].specs[3].expectationResults[0].message.search(/fakeObject3/) !== -1), + 'Fourth test should have contained /fakeObject3/, got ' + runner.suites[0].specs[3].expectationResults[0].message); +}; var testResultsAliasing = function () { - jasmine = Jasmine.init(); + Jasmine.currentEnv_ = new Jasmine.Env(); var runner = Runner(); describe('Suite for result aliasing test', function () { it('should be a test', function() { runs(function () { - this.expects_that(true).should_equal(true); + expect(true).toEqual(true); }); }); @@ -926,13 +1774,13 @@ var testResultsAliasing = function () { describe('Suite number two for result aliasing test', function () { it('should be a passing test', function() { runs(function () { - this.expects_that(true).should_equal(true); + expect(true).toEqual(true); }); }); it('should be a passing test', function() { runs(function () { - this.expects_that(true).should_equal(true); + expect(true).toEqual(true); }); }); @@ -941,74 +1789,75 @@ var testResultsAliasing = function () { runner.execute(); - setTimeout(function() { + reporter.test((runner.suiteResults !== undefined), + 'runner.suiteResults was not defined'); - reporter.test((runner.suiteResults !== undefined), - 'runner.suiteResults was not defined'); + reporter.test((runner.suiteResults == runner.results.results), + 'runner.suiteResults should have been ' + reporter.toJSON(runner.results.results) + + ', but was ' + reporter.toJSON(runner.suiteResults)); - reporter.test((runner.suiteResults == runner.results.results), - 'runner.suiteResults should have been ' + Object.toJSON(runner.results.results) + - ', but was ' + Object.toJSON(runner.suiteResults)); + reporter.test((runner.suiteResults[1] == runner.results.results[1]), + 'runner.suiteResults should have been ' + reporter.toJSON(runner.results.results[1]) + + ', but was ' + reporter.toJSON(runner.suiteResults[1])); - reporter.test((runner.suiteResults[1] == runner.results.results[1]), - 'runner.suiteResults should have been ' + Object.toJSON(runner.results.results[1]) + - ', but was ' + Object.toJSON(runner.suiteResults[1])); + reporter.test((runner.suites[0].specResults !== undefined), + 'runner.suites[0].specResults was not defined'); - reporter.test((runner.suites[0].specResults !== undefined), - 'runner.suites[0].specResults was not defined'); + reporter.test((runner.suites[0].specResults == runner.results.results[0].results), + 'runner.suites[0].specResults should have been ' + reporter.toJSON(runner.results.results[0].results) + + ', but was ' + reporter.toJSON(runner.suites[0].specResults)); - reporter.test((runner.suites[0].specResults == runner.results.results[0].results), - 'runner.suites[0].specResults should have been ' + Object.toJSON(runner.results.results[0].results) + - ', but was ' + Object.toJSON(runner.suites[0].specResults)); + reporter.test((runner.suites[0].specs[0].expectationResults !== undefined), + 'runner.suites[0].specs[0].expectationResults was not defined'); - reporter.test((runner.suites[0].specs[0].expectationResults !== undefined), - 'runner.suites[0].specs[0].expectationResults was not defined'); + reporter.test((runner.suites[0].specs[0].expectationResults == runner.results.results[0].results[0].results), + 'runner.suites[0].specs[0].expectationResults should have been ' + reporter.toJSON(runner.results.results[0].results[0].results) + + ', but was ' + reporter.toJSON(runner.suites[0].specs[0].expectationResults)); - reporter.test((runner.suites[0].specs[0].expectationResults == runner.results.results[0].results[0].results), - 'runner.suites[0].specs[0].expectationResults should have been ' + Object.toJSON(runner.results.results[0].results[0].results) + - ', but was ' + Object.toJSON(runner.suites[0].specs[0].expectationResults)); - - }, 250); -} +}; var runTests = function () { - $('spinner').show(); + document.getElementById('spinner').style.display = ""; + testMatchersPrettyPrinting(); testMatchersComparisons(); testMatchersReporting(); + testDisabledSpecs(); + testDisabledSuites(); testSpecs(); + testSpecsWithoutRunsBlock(); testAsyncSpecs(); testAsyncSpecsWithMockSuite(); + testWaitsFor(); + testWaitsForFailsWithMessage(); + testWaitsForFailsIfTimeout(); + testSpecAfter(); testSuites(); testBeforeAndAfterCallbacks(); + testBeforeExecutesSafely(); + testAfterExecutesSafely(); testSpecScope(); testRunner(); testRunnerFinishCallback(); testNestedResults(); testResults(); -// handle blank specs will work later. - // testHandlesBlankSpecs(); testFormatsExceptionMessages(); testHandlesExceptions(); testResultsAliasing(); + testReporterWithCallbacks(); + testJSONReporter(); + testJSONReporterWithDOM(); + testSpecSpy(); + testExplodes(); -// Timing starts to matter with these tests; ALWAYS use setTimeout() - setTimeout(function () { - testReporterWithCallbacks(); - }, 2500); - setTimeout(function () { - testJSONReporter(); - }, 3500); - setTimeout(function () { - testJSONReporterWithDOM(); - }, 5000); - - setTimeout(function() { - $('spinner').hide(); - reporter.summary(); - }, 6000); -} + // handle blank specs will work later. + // testHandlesBlankSpecs(); + reporter.summary(); + document.getElementById('spinner').style.display = "none"; + +}; + diff --git a/test/jsUnitMockTimeout.js b/test/jsUnitMockTimeout.js new file mode 100755 index 0000000..99a4bf1 --- /dev/null +++ b/test/jsUnitMockTimeout.js @@ -0,0 +1,81 @@ +// Mock setTimeout, clearTimeout +// Contributed by Pivotal Computer Systems, www.pivotalsf.com + +var Clock = { + timeoutsMade: 0, + scheduledFunctions: {}, + nowMillis: 0, + reset: function() { + this.scheduledFunctions = {}; + this.nowMillis = 0; + this.timeoutsMade = 0; + }, + tick: function(millis) { + var oldMillis = this.nowMillis; + var newMillis = oldMillis + millis; + this.runFunctionsWithinRange(oldMillis, newMillis); + this.nowMillis = newMillis; + }, + runFunctionsWithinRange: function(oldMillis, nowMillis) { + var scheduledFunc; + var funcsToRun = []; + for (var timeoutKey in this.scheduledFunctions) { + scheduledFunc = this.scheduledFunctions[timeoutKey]; + if (scheduledFunc != undefined && + scheduledFunc.runAtMillis >= oldMillis && + scheduledFunc.runAtMillis <= nowMillis) { + funcsToRun.push(scheduledFunc); + this.scheduledFunctions[timeoutKey] = undefined; + } + } + + if (funcsToRun.length > 0) { + funcsToRun.sort(function(a, b) { + return a.runAtMillis - b.runAtMillis; + }); + for (var i = 0; i < funcsToRun.length; ++i) { + try { + this.nowMillis = funcsToRun[i].runAtMillis; + funcsToRun[i].funcToCall(); + if (funcsToRun[i].recurring) { + Clock.scheduleFunction(funcsToRun[i].timeoutKey, + funcsToRun[i].funcToCall, + funcsToRun[i].millis, + true); + } + } catch(e) { + } + } + this.runFunctionsWithinRange(oldMillis, nowMillis); + } + }, + scheduleFunction: function(timeoutKey, funcToCall, millis, recurring) { + Clock.scheduledFunctions[timeoutKey] = { + runAtMillis: Clock.nowMillis + millis, + funcToCall: funcToCall, + recurring: recurring, + timeoutKey: timeoutKey, + millis: millis + }; + } +}; + +function setTimeout(funcToCall, millis) { + Clock.timeoutsMade = Clock.timeoutsMade + 1; + Clock.scheduleFunction(Clock.timeoutsMade, funcToCall, millis, false); + return Clock.timeoutsMade; +} + +function setInterval(funcToCall, millis) { + Clock.timeoutsMade = Clock.timeoutsMade + 1; + Clock.scheduleFunction(Clock.timeoutsMade, funcToCall, millis, true); + return Clock.timeoutsMade; +} + +function clearTimeout(timeoutKey) { + Clock.scheduledFunctions[timeoutKey] = undefined; +} + +function clearInterval(timeoutKey) { + Clock.scheduledFunctions[timeoutKey] = undefined; +} diff --git a/test/json2.js b/test/json2.js new file mode 100644 index 0000000..241a271 --- /dev/null +++ b/test/json2.js @@ -0,0 +1,478 @@ +/* + http://www.JSON.org/json2.js + 2008-11-19 + + Public Domain. + + NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. + + See http://www.JSON.org/js.html + + This file creates a global JSON object containing two methods: stringify + and parse. + + JSON.stringify(value, replacer, space) + value any JavaScript value, usually an object or array. + + replacer an optional parameter that determines how object + values are stringified for objects. It can be a + function or an array of strings. + + space an optional parameter that specifies the indentation + of nested structures. If it is omitted, the text will + be packed without extra whitespace. If it is a number, + it will specify the number of spaces to indent at each + level. If it is a string (such as '\t' or ' '), + it contains the characters used to indent at each level. + + This method produces a JSON text from a JavaScript value. + + When an object value is found, if the object contains a toJSON + method, its toJSON method will be called and the result will be + stringified. A toJSON method does not serialize: it returns the + value represented by the name/value pair that should be serialized, + or undefined if nothing should be serialized. The toJSON method + will be passed the key associated with the value, and this will be + bound to the object holding the key. + + For example, this would serialize Dates as ISO strings. + + Date.prototype.toJSON = function (key) { + function f(n) { + // Format integers to have at least two digits. + return n < 10 ? '0' + n : n; + } + + return this.getUTCFullYear() + '-' + + f(this.getUTCMonth() + 1) + '-' + + f(this.getUTCDate()) + 'T' + + f(this.getUTCHours()) + ':' + + f(this.getUTCMinutes()) + ':' + + f(this.getUTCSeconds()) + 'Z'; + }; + + You can provide an optional replacer method. It will be passed the + key and value of each member, with this bound to the containing + object. The value that is returned from your method will be + serialized. If your method returns undefined, then the member will + be excluded from the serialization. + + If the replacer parameter is an array of strings, then it will be + used to select the members to be serialized. It filters the results + such that only members with keys listed in the replacer array are + stringified. + + Values that do not have JSON representations, such as undefined or + functions, will not be serialized. Such values in objects will be + dropped; in arrays they will be replaced with null. You can use + a replacer function to replace those with JSON values. + JSON.stringify(undefined) returns undefined. + + The optional space parameter produces a stringification of the + value that is filled with line breaks and indentation to make it + easier to read. + + If the space parameter is a non-empty string, then that string will + be used for indentation. If the space parameter is a number, then + the indentation will be that many spaces. + + Example: + + text = JSON.stringify(['e', {pluribus: 'unum'}]); + // text is '["e",{"pluribus":"unum"}]' + + + text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); + // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' + + text = JSON.stringify([new Date()], function (key, value) { + return this[key] instanceof Date ? + 'Date(' + this[key] + ')' : value; + }); + // text is '["Date(---current time---)"]' + + + JSON.parse(text, reviver) + This method parses a JSON text to produce an object or array. + It can throw a SyntaxError exception. + + The optional reviver parameter is a function that can filter and + transform the results. It receives each of the keys and values, + and its return value is used instead of the original value. + If it returns what it received, then the structure is not modified. + If it returns undefined then the member is deleted. + + Example: + + // Parse the text. Values that look like ISO date strings will + // be converted to Date objects. + + myData = JSON.parse(text, function (key, value) { + var a; + if (typeof value === 'string') { + a = +/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); + if (a) { + return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], + +a[5], +a[6])); + } + } + return value; + }); + + myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { + var d; + if (typeof value === 'string' && + value.slice(0, 5) === 'Date(' && + value.slice(-1) === ')') { + d = new Date(value.slice(5, -1)); + if (d) { + return d; + } + } + return value; + }); + + + This is a reference implementation. You are free to copy, modify, or + redistribute. + + This code should be minified before deployment. + See http://javascript.crockford.com/jsmin.html + + USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO + NOT CONTROL. +*/ + +/*jslint evil: true */ + +/*global JSON */ + +/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, + call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, + getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, + lastIndex, length, parse, prototype, push, replace, slice, stringify, + test, toJSON, toString, valueOf +*/ + +// Create a JSON object only if one does not already exist. We create the +// methods in a closure to avoid creating global variables. + +if (!this.JSON) { + JSON = {}; +} +(function () { + + function f(n) { + // Format integers to have at least two digits. + return n < 10 ? '0' + n : n; + } + + if (typeof Date.prototype.toJSON !== 'function') { + + Date.prototype.toJSON = function (key) { + + return this.getUTCFullYear() + '-' + + f(this.getUTCMonth() + 1) + '-' + + f(this.getUTCDate()) + 'T' + + f(this.getUTCHours()) + ':' + + f(this.getUTCMinutes()) + ':' + + f(this.getUTCSeconds()) + 'Z'; + }; + + String.prototype.toJSON = + Number.prototype.toJSON = + Boolean.prototype.toJSON = function (key) { + return this.valueOf(); + }; + } + + var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, + escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, + gap, + indent, + meta = { // table of character substitutions + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '"' : '\\"', + '\\': '\\\\' + }, + rep; + + + function quote(string) { + +// If the string contains no control characters, no quote characters, and no +// backslash characters, then we can safely slap some quotes around it. +// Otherwise we must also replace the offending characters with safe escape +// sequences. + + escapable.lastIndex = 0; + return escapable.test(string) ? + '"' + string.replace(escapable, function (a) { + var c = meta[a]; + return typeof c === 'string' ? c : + '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }) + '"' : + '"' + string + '"'; + } + + + function str(key, holder) { + +// Produce a string from holder[key]. + + var i, // The loop counter. + k, // The member key. + v, // The member value. + length, + mind = gap, + partial, + value = holder[key]; + +// If the value has a toJSON method, call it to obtain a replacement value. + + if (value && typeof value === 'object' && + typeof value.toJSON === 'function') { + value = value.toJSON(key); + } + +// If we were called with a replacer function, then call the replacer to +// obtain a replacement value. + + if (typeof rep === 'function') { + value = rep.call(holder, key, value); + } + +// What happens next depends on the value's type. + + switch (typeof value) { + case 'string': + return quote(value); + + case 'number': + +// JSON numbers must be finite. Encode non-finite numbers as null. + + return isFinite(value) ? String(value) : 'null'; + + case 'boolean': + case 'null': + +// If the value is a boolean or null, convert it to a string. Note: +// typeof null does not produce 'null'. The case is included here in +// the remote chance that this gets fixed someday. + + return String(value); + +// If the type is 'object', we might be dealing with an object or an array or +// null. + + case 'object': + +// Due to a specification blunder in ECMAScript, typeof null is 'object', +// so watch out for that case. + + if (!value) { + return 'null'; + } + +// Make an array to hold the partial results of stringifying this object value. + + gap += indent; + partial = []; + +// Is the value an array? + + if (Object.prototype.toString.apply(value) === '[object Array]') { + +// The value is an array. Stringify every element. Use null as a placeholder +// for non-JSON values. + + length = value.length; + for (i = 0; i < length; i += 1) { + partial[i] = str(i, value) || 'null'; + } + +// Join all of the elements together, separated with commas, and wrap them in +// brackets. + + v = partial.length === 0 ? '[]' : + gap ? '[\n' + gap + + partial.join(',\n' + gap) + '\n' + + mind + ']' : + '[' + partial.join(',') + ']'; + gap = mind; + return v; + } + +// If the replacer is an array, use it to select the members to be stringified. + + if (rep && typeof rep === 'object') { + length = rep.length; + for (i = 0; i < length; i += 1) { + k = rep[i]; + if (typeof k === 'string') { + v = str(k, value); + if (v) { + partial.push(quote(k) + (gap ? ': ' : ':') + v); + } + } + } + } else { + +// Otherwise, iterate through all of the keys in the object. + + for (k in value) { + if (Object.hasOwnProperty.call(value, k)) { + v = str(k, value); + if (v) { + partial.push(quote(k) + (gap ? ': ' : ':') + v); + } + } + } + } + +// Join all of the member texts together, separated with commas, +// and wrap them in braces. + + v = partial.length === 0 ? '{}' : + gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + + mind + '}' : '{' + partial.join(',') + '}'; + gap = mind; + return v; + } + } + +// If the JSON object does not yet have a stringify method, give it one. + + if (typeof JSON.stringify !== 'function') { + JSON.stringify = function (value, replacer, space) { + +// The stringify method takes a value and an optional replacer, and an optional +// space parameter, and returns a JSON text. The replacer can be a function +// that can replace values, or an array of strings that will select the keys. +// A default replacer method can be provided. Use of the space parameter can +// produce text that is more easily readable. + + var i; + gap = ''; + indent = ''; + +// If the space parameter is a number, make an indent string containing that +// many spaces. + + if (typeof space === 'number') { + for (i = 0; i < space; i += 1) { + indent += ' '; + } + +// If the space parameter is a string, it will be used as the indent string. + + } else if (typeof space === 'string') { + indent = space; + } + +// If there is a replacer, it must be a function or an array. +// Otherwise, throw an error. + + rep = replacer; + if (replacer && typeof replacer !== 'function' && + (typeof replacer !== 'object' || + typeof replacer.length !== 'number')) { + throw new Error('JSON.stringify'); + } + +// Make a fake root object containing our value under the key of ''. +// Return the result of stringifying the value. + + return str('', {'': value}); + }; + } + + +// If the JSON object does not yet have a parse method, give it one. + + if (typeof JSON.parse !== 'function') { + JSON.parse = function (text, reviver) { + +// The parse method takes a text and an optional reviver function, and returns +// a JavaScript value if the text is a valid JSON text. + + var j; + + function walk(holder, key) { + +// The walk method is used to recursively walk the resulting structure so +// that modifications can be made. + + var k, v, value = holder[key]; + if (value && typeof value === 'object') { + for (k in value) { + if (Object.hasOwnProperty.call(value, k)) { + v = walk(value, k); + if (v !== undefined) { + value[k] = v; + } else { + delete value[k]; + } + } + } + } + return reviver.call(holder, key, value); + } + + +// Parsing happens in four stages. In the first stage, we replace certain +// Unicode characters with escape sequences. JavaScript handles many characters +// incorrectly, either silently deleting them, or treating them as line endings. + + cx.lastIndex = 0; + if (cx.test(text)) { + text = text.replace(cx, function (a) { + return '\\u' + + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }); + } + +// In the second stage, we run the text against regular expressions that look +// for non-JSON patterns. We are especially concerned with '()' and 'new' +// because they can cause invocation, and '=' because it can cause mutation. +// But just to be safe, we want to reject all unexpected forms. + +// We split the second stage into 4 regexp operations in order to work around +// crippling inefficiencies in IE's and Safari's regexp engines. First we +// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we +// replace all simple value tokens with ']' characters. Third, we delete all +// open brackets that follow a colon or comma or that begin the text. Finally, +// we look to see that the remaining characters are only whitespace or ']' or +// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. + + if (/^[\],:{}\s]*$/. +test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@'). +replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']'). +replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { + +// In the third stage we use the eval function to compile the text into a +// JavaScript structure. The '{' operator is subject to a syntactic ambiguity +// in JavaScript: it can begin a block or an object literal. We wrap the text +// in parens to eliminate the ambiguity. + + j = eval('(' + text + ')'); + +// In the optional fourth stage, we recursively walk the new structure, passing +// each name/value pair to a reviver function for possible transformation. + + return typeof reviver === 'function' ? + walk({'': j}, '') : j; + } + +// If the text is not JSON parseable, then a SyntaxError is thrown. + + throw new SyntaxError('JSON.parse'); + }; + } +})();