diff --git a/vendor/scripts/jasmine-boot.js b/vendor/scripts/jasmine-boot.js index 47b125211..17ec5589a 100755 --- a/vendor/scripts/jasmine-boot.js +++ b/vendor/scripts/jasmine-boot.js @@ -1,5 +1,29 @@ +// Changes made to this file from the default are marked with CHANGE comment + +/* + Copyright (c) 2008-2015 Pivotal Labs + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ /** - Starting with version 2.0, this file "boots" Jasmine, performing all of the necessary initialization before executing the loaded environment and all of a project's specs. This file should be loaded after `jasmine.js`, but before any project source files or spec files are loaded. Thus this file can also be used to customize Jasmine for a project. + Starting with version 2.0, this file "boots" Jasmine, performing all of the necessary initialization before executing the loaded environment and all of a project's specs. This file should be loaded after `jasmine.js` and `jasmine_html.js`, but before any project source files or spec files are loaded. Thus this file can also be used to customize Jasmine for a project. If a project is using Jasmine via the standalone distribution, this file can be customized directly. If a project is using Jasmine via the [Ruby gem][jasmine-gem], this file can be copied into the support directory via `jasmine copy_boot_js`. Other environments (e.g., Python) will have different mechanisms. @@ -32,77 +56,12 @@ * * Build up the functions that will be exposed as the Jasmine public interface. A project can customize, rename or alias any of these functions as desired, provided the implementation remains unchanged. */ - var jasmineInterface = { - describe: function(description, specDefinitions) { - return env.describe(description, specDefinitions); - }, - - xdescribe: function(description, specDefinitions) { - return env.xdescribe(description, specDefinitions); - }, - - it: function(desc, func) { - return env.it(desc, func); - }, - - xit: function(desc, func) { - return env.xit(desc, func); - }, - - beforeEach: function(beforeEachFunction) { - return env.beforeEach(beforeEachFunction); - }, - - afterEach: function(afterEachFunction) { - return env.afterEach(afterEachFunction); - }, - - expect: function(actual) { - return env.expect(actual); - }, - - pending: function() { - return env.pending(); - }, - - spyOn: function(obj, methodName) { - return env.spyOn(obj, methodName); - }, - - jsApiReporter: new jasmine.JsApiReporter({ - timer: new jasmine.Timer() - }) - }; + var jasmineInterface = jasmineRequire.interface(jasmine, env); /** - * Add all of the Jasmine global/public interface to the proper global, so a project can use the public interface directly. For example, calling `describe` in specs instead of `jasmine.getEnv().describe`. + * Add all of the Jasmine global/public interface to the global scope, so a project can use the public interface directly. For example, calling `describe` in specs instead of `jasmine.getEnv().describe`. */ - if (typeof window == "undefined" && typeof exports == "object") { - extend(exports, jasmineInterface); - } else { - extend(window, jasmineInterface); - } - - /** - * Expose the interface for adding custom equality testers. - */ - jasmine.addCustomEqualityTester = function(tester) { - env.addCustomEqualityTester(tester); - }; - - /** - * Expose the interface for adding custom expectation matchers - */ - jasmine.addMatchers = function(matchers) { - return env.addMatchers(matchers); - }; - - /** - * Expose the mock interface for the JavaScript timeout functions - */ - jasmine.clock = function() { - return env.clock; - }; + extend(window, jasmineInterface); /** * ## Runner Parameters @@ -114,16 +73,32 @@ getWindowLocation: function() { return window.location; } }); - var catchingExceptions = false; // Setting to false always for CodeCombat, because Jasmine-HTML's reporter doesn't do source maps, and Chrome console does. + // CHANGE: Setting to false always, because Jasmine-HTML's reporter doesn't do source maps, and Chrome console does. + var catchingExceptions = false; env.catchExceptions(typeof catchingExceptions === "undefined" ? true : catchingExceptions); + var throwingExpectationFailures = queryString.getParam("throwFailures"); + env.throwOnExpectationFailure(throwingExpectationFailures); + + var random = queryString.getParam("random"); + env.randomizeTests(random); + + var seed = queryString.getParam("seed"); + if (seed) { + env.seed(seed); + } + /** * ## Reporters * The `HtmlReporter` builds all of the HTML UI for the runner page. This reporter paints the dots, stars, and x's for specs, as well as all spec names and all failures (if any). */ var htmlReporter = new jasmine.HtmlReporter({ env: env, - onRaiseExceptionsClick: function() { queryString.setParam("catch", !env.catchingExceptions()); }, + onRaiseExceptionsClick: function() { queryString.navigateWithNewParam("catch", !env.catchingExceptions()); }, + onThrowExpectationsClick: function() { queryString.navigateWithNewParam("throwFailures", !env.throwingExpectationFailures()); }, + onRandomClick: function() { queryString.navigateWithNewParam("random", !env.randomTests()); }, + addToExistingQueryString: function(key, value) { return queryString.fullStringWithNewParam(key, value); }, + // CHANGE: put #testing-area, not body getContainer: function() { return $('#testing-area')[0]; }, createElement: function() { return document.createElement.apply(document, arguments); }, createTextNode: function() { return document.createTextNode.apply(document, arguments); }, @@ -160,6 +135,7 @@ * * Replace the browser window's `onload`, ensure it's called, and then run all of the loaded specs. This includes initializing the `HtmlReporter` instance and then executing the loaded Jasmine environment. All of this will happen after all of the specs are loaded. */ + // CHANGE: give start control to testing page window.runJasmine = function() { htmlReporter.initialize(); env.execute(); @@ -173,4 +149,4 @@ return destination; } -}()); +}()); \ No newline at end of file diff --git a/vendor/scripts/jasmine.js b/vendor/scripts/jasmine.js index 24463ecb8..ad63dff7c 100755 --- a/vendor/scripts/jasmine.js +++ b/vendor/scripts/jasmine.js @@ -1,83 +1,107 @@ /* -Copyright (c) 2008-2013 Pivotal Labs + Copyright (c) 2008-2015 Pivotal Labs -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ -function getJasmineRequireObj() { - if (typeof module !== "undefined" && module.exports) { - return exports; + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +var getJasmineRequireObj = (function (jasmineGlobal) { + var jasmineRequire; + + if (typeof module !== 'undefined' && module.exports) { + if (typeof global !== 'undefined') { + jasmineGlobal = global; + } else { + jasmineGlobal = {}; + } + jasmineRequire = exports; } else { - window.jasmineRequire = window.jasmineRequire || {}; - return window.jasmineRequire; + if (typeof window !== 'undefined' && typeof window.toString === 'function' && window.toString() === '[object GjsGlobal]') { + jasmineGlobal = window; + } + jasmineRequire = jasmineGlobal.jasmineRequire = jasmineGlobal.jasmineRequire || {}; } -} -getJasmineRequireObj().core = function(jRequire) { - var j$ = {}; + function getJasmineRequire() { + return jasmineRequire; + } - jRequire.base(j$); - j$.util = jRequire.util(); - j$.Any = jRequire.Any(); - j$.CallTracker = jRequire.CallTracker(); - j$.Clock = jRequire.Clock(); - j$.DelayedFunctionScheduler = jRequire.DelayedFunctionScheduler(); - j$.Env = jRequire.Env(j$); - j$.ExceptionFormatter = jRequire.ExceptionFormatter(); - j$.Expectation = jRequire.Expectation(); - j$.buildExpectationResult = jRequire.buildExpectationResult(); - j$.JsApiReporter = jRequire.JsApiReporter(); - j$.matchersUtil = jRequire.matchersUtil(j$); - j$.ObjectContaining = jRequire.ObjectContaining(j$); - j$.pp = jRequire.pp(j$); - j$.QueueRunner = jRequire.QueueRunner(); - j$.ReportDispatcher = jRequire.ReportDispatcher(); - j$.Spec = jRequire.Spec(j$); - j$.SpyStrategy = jRequire.SpyStrategy(); - j$.Suite = jRequire.Suite(); - j$.Timer = jRequire.Timer(); - j$.version = jRequire.version(); + getJasmineRequire().core = function(jRequire) { + var j$ = {}; - j$.matchers = jRequire.requireMatchers(jRequire, j$); + jRequire.base(j$, jasmineGlobal); + j$.util = jRequire.util(); + j$.errors = jRequire.errors(); + j$.Any = jRequire.Any(j$); + j$.Anything = jRequire.Anything(j$); + j$.CallTracker = jRequire.CallTracker(); + j$.MockDate = jRequire.MockDate(); + j$.Clock = jRequire.Clock(); + j$.DelayedFunctionScheduler = jRequire.DelayedFunctionScheduler(); + j$.Env = jRequire.Env(j$); + j$.ExceptionFormatter = jRequire.ExceptionFormatter(); + j$.Expectation = jRequire.Expectation(); + j$.buildExpectationResult = jRequire.buildExpectationResult(); + j$.JsApiReporter = jRequire.JsApiReporter(); + j$.matchersUtil = jRequire.matchersUtil(j$); + j$.ObjectContaining = jRequire.ObjectContaining(j$); + j$.ArrayContaining = jRequire.ArrayContaining(j$); + j$.pp = jRequire.pp(j$); + j$.QueueRunner = jRequire.QueueRunner(j$); + j$.ReportDispatcher = jRequire.ReportDispatcher(); + j$.Spec = jRequire.Spec(j$); + j$.SpyRegistry = jRequire.SpyRegistry(j$); + j$.SpyStrategy = jRequire.SpyStrategy(); + j$.StringMatching = jRequire.StringMatching(j$); + j$.Suite = jRequire.Suite(j$); + j$.Timer = jRequire.Timer(); + j$.TreeProcessor = jRequire.TreeProcessor(); + j$.version = jRequire.version(); + j$.Order = jRequire.Order(); - return j$; -}; + j$.matchers = jRequire.requireMatchers(jRequire, j$); + + return j$; + }; + + return getJasmineRequire; +})(this); getJasmineRequireObj().requireMatchers = function(jRequire, j$) { var availableMatchers = [ - "toBe", - "toBeCloseTo", - "toBeDefined", - "toBeFalsy", - "toBeGreaterThan", - "toBeLessThan", - "toBeNaN", - "toBeNull", - "toBeTruthy", - "toBeUndefined", - "toContain", - "toEqual", - "toHaveBeenCalled", - "toHaveBeenCalledWith", - "toMatch", - "toThrow", - "toThrowError" + 'toBe', + 'toBeCloseTo', + 'toBeDefined', + 'toBeFalsy', + 'toBeGreaterThan', + 'toBeLessThan', + 'toBeNaN', + 'toBeNull', + 'toBeTruthy', + 'toBeUndefined', + 'toContain', + 'toEqual', + 'toHaveBeenCalled', + 'toHaveBeenCalledWith', + 'toHaveBeenCalledTimes', + 'toMatch', + 'toThrow', + 'toThrowError' ], matchers = {}; @@ -89,20 +113,18 @@ getJasmineRequireObj().requireMatchers = function(jRequire, j$) { return matchers; }; -getJasmineRequireObj().base = function(j$) { +getJasmineRequireObj().base = function(j$, jasmineGlobal) { j$.unimplementedMethod_ = function() { - throw new Error("unimplemented method"); + throw new Error('unimplemented method'); }; j$.MAX_PRETTY_PRINT_DEPTH = 40; + j$.MAX_PRETTY_PRINT_ARRAY_LENGTH = 100; j$.DEFAULT_TIMEOUT_INTERVAL = 5000; - j$.getGlobal = (function() { - var jasmineGlobal = eval.call(null, "this"); - return function() { - return jasmineGlobal; - }; - })(); + j$.getGlobal = function() { + return jasmineGlobal; + }; j$.getEnv = function(options) { var env = j$.currentEnv_ = j$.currentEnv_ || new j$.Env(options); @@ -111,15 +133,15 @@ getJasmineRequireObj().base = function(j$) { }; j$.isArray_ = function(value) { - return j$.isA_("Array", value); + return j$.isA_('Array', value); }; j$.isString_ = function(value) { - return j$.isA_("String", value); + return j$.isA_('String', value); }; j$.isNumber_ = function(value) { - return j$.isA_("Number", value); + return j$.isA_('Number', value); }; j$.isA_ = function(typeName, value) { @@ -130,14 +152,30 @@ getJasmineRequireObj().base = function(j$) { return obj.nodeType > 0; }; + j$.fnNameFor = function(func) { + return func.name || func.toString().match(/^\s*function\s*(\w*)\s*\(/)[1]; + }; + j$.any = function(clazz) { return new j$.Any(clazz); }; + j$.anything = function() { + return new j$.Anything(); + }; + j$.objectContaining = function(sample) { return new j$.ObjectContaining(sample); }; + j$.stringMatching = function(expected) { + return new j$.StringMatching(expected); + }; + + j$.arrayContaining = function(sample) { + return new j$.ArrayContaining(sample); + }; + j$.createSpy = function(name, originalFn) { var spyStrategy = new j$.SpyStrategy({ @@ -147,16 +185,21 @@ getJasmineRequireObj().base = function(j$) { }), callTracker = new j$.CallTracker(), spy = function() { - callTracker.track({ + var callData = { object: this, args: Array.prototype.slice.apply(arguments) - }); - return spyStrategy.exec.apply(this, arguments); + }; + + callTracker.track(callData); + var returnValue = spyStrategy.exec.apply(this, arguments); + callData.returnValue = returnValue; + + return returnValue; }; for (var prop in originalFn) { if (prop === 'and' || prop === 'calls') { - throw new Error("Jasmine spies would overwrite the 'and' and 'calls' properties on the object being spied upon"); + throw new Error('Jasmine spies would overwrite the \'and\' and \'calls\' properties on the object being spied upon'); } spy[prop] = originalFn[prop]; @@ -177,8 +220,13 @@ getJasmineRequireObj().base = function(j$) { }; j$.createSpyObj = function(baseName, methodNames) { + if (j$.isArray_(baseName) && j$.util.isUndefined(methodNames)) { + methodNames = baseName; + baseName = 'unknown'; + } + if (!j$.isArray_(methodNames) || methodNames.length === 0) { - throw "createSpyObj requires a non-empty array of method names to create spies for"; + throw 'createSpyObj requires a non-empty array of method names to create spies for'; } var obj = {}; for (var i = 0; i < methodNames.length; i++) { @@ -220,6 +268,31 @@ getJasmineRequireObj().util = function() { return obj === void 0; }; + util.arrayContains = function(array, search) { + var i = array.length; + while (i--) { + if (array[i] === search) { + return true; + } + } + return false; + }; + + util.clone = function(obj) { + if (Object.prototype.toString.apply(obj) === '[object Array]') { + return obj.slice(); + } + + var cloned = {}; + for (var prop in obj) { + if (obj.hasOwnProperty(prop)) { + cloned[prop] = obj[prop]; + } + } + + return cloned; + }; + return util; }; @@ -229,19 +302,17 @@ getJasmineRequireObj().Spec = function(j$) { this.resultCallback = attrs.resultCallback || function() {}; this.id = attrs.id; this.description = attrs.description || ''; - this.fn = attrs.fn; - this.beforeFns = attrs.beforeFns || function() { return []; }; - this.afterFns = attrs.afterFns || function() { return []; }; + this.queueableFn = attrs.queueableFn; + this.beforeAndAfterFns = attrs.beforeAndAfterFns || function() { return {befores: [], afters: []}; }; + this.userContext = attrs.userContext || function() { return {}; }; this.onStart = attrs.onStart || function() {}; - this.exceptionFormatter = attrs.exceptionFormatter || function() {}; this.getSpecName = attrs.getSpecName || function() { return ''; }; this.expectationResultFactory = attrs.expectationResultFactory || function() { }; this.queueRunnerFactory = attrs.queueRunnerFactory || function() {}; this.catchingExceptions = attrs.catchingExceptions || function() { return true; }; + this.throwOnExpectationFailure = !!attrs.throwOnExpectationFailure; - this.timer = attrs.timer || {setTimeout: setTimeout, clearTimeout: clearTimeout}; - - if (!this.fn) { + if (!this.queueableFn.fn) { this.pend(); } @@ -249,84 +320,51 @@ getJasmineRequireObj().Spec = function(j$) { id: this.id, description: this.description, fullName: this.getFullName(), - failedExpectations: [] + failedExpectations: [], + passedExpectations: [], + pendingReason: '' }; } - Spec.prototype.addExpectationResult = function(passed, data) { + Spec.prototype.addExpectationResult = function(passed, data, isError) { + var expectationResult = this.expectationResultFactory(data); if (passed) { - return; + this.result.passedExpectations.push(expectationResult); + } else { + this.result.failedExpectations.push(expectationResult); + + if (this.throwOnExpectationFailure && !isError) { + throw new j$.errors.ExpectationFailed(); + } } - this.result.failedExpectations.push(this.expectationResultFactory(data)); }; Spec.prototype.expect = function(actual) { return this.expectationFactory(actual, this); }; - Spec.prototype.execute = function(onComplete) { - var self = this, - timeout; + Spec.prototype.execute = function(onComplete, enabled) { + var self = this; this.onStart(this); - if (this.markedPending || this.disabled) { - complete(); + if (!this.isExecutable() || this.markedPending || enabled === false) { + complete(enabled); return; } - function timeoutable(fn) { - return function(done) { - timeout = Function.prototype.apply.apply(self.timer.setTimeout, [j$.getGlobal(), [function() { - onException(new Error('Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.')); - done(); - }, j$.DEFAULT_TIMEOUT_INTERVAL]]); - - var callDone = function() { - clearTimeoutable(); - done(); - }; - - fn.call(this, callDone); //TODO: do we care about more than 1 arg? - }; - } - - function clearTimeoutable() { - Function.prototype.apply.apply(self.timer.clearTimeout, [j$.getGlobal(), [timeout]]); - timeout = void 0; - } - - var allFns = this.beforeFns().concat(this.fn).concat(this.afterFns()), - allTimeoutableFns = []; - for (var i = 0; i < allFns.length; i++) { - var fn = allFns[i]; - allTimeoutableFns.push(fn.length > 0 ? timeoutable(fn) : fn); - } + var fns = this.beforeAndAfterFns(); + var allFns = fns.befores.concat(this.queueableFn).concat(fns.afters); this.queueRunnerFactory({ - fns: allTimeoutableFns, - onException: onException, - onComplete: complete + queueableFns: allFns, + onException: function() { self.onException.apply(self, arguments); }, + onComplete: complete, + userContext: this.userContext() }); - function onException(e) { - clearTimeoutable(); - if (Spec.isPendingSpecException(e)) { - self.pend(); - return; - } - - self.addExpectationResult(false, { - matcherName: "", - passed: false, - expected: "", - actual: "", - error: e - }); - } - - function complete() { - self.result.status = self.status(); + function complete(enabledAgain) { + self.result.status = self.status(enabledAgain); self.resultCallback(self.result); if (onComplete) { @@ -335,16 +373,43 @@ getJasmineRequireObj().Spec = function(j$) { } }; + Spec.prototype.onException = function onException(e) { + if (Spec.isPendingSpecException(e)) { + this.pend(extractCustomPendingMessage(e)); + return; + } + + if (e instanceof j$.errors.ExpectationFailed) { + return; + } + + this.addExpectationResult(false, { + matcherName: '', + passed: false, + expected: '', + actual: '', + error: e + }, true); + }; + Spec.prototype.disable = function() { this.disabled = true; }; - Spec.prototype.pend = function() { + Spec.prototype.pend = function(message) { this.markedPending = true; + if (message) { + this.result.pendingReason = message; + } }; - Spec.prototype.status = function() { - if (this.disabled) { + Spec.prototype.getResult = function() { + this.result.status = this.status(); + return this.result; + }; + + Spec.prototype.status = function(enabled) { + if (this.disabled || enabled === false) { return 'disabled'; } @@ -359,23 +424,82 @@ getJasmineRequireObj().Spec = function(j$) { } }; + Spec.prototype.isExecutable = function() { + return !this.disabled; + }; + Spec.prototype.getFullName = function() { return this.getSpecName(this); }; - Spec.pendingSpecExceptionMessage = "=> marked Pending"; + var extractCustomPendingMessage = function(e) { + var fullMessage = e.toString(), + boilerplateStart = fullMessage.indexOf(Spec.pendingSpecExceptionMessage), + boilerplateEnd = boilerplateStart + Spec.pendingSpecExceptionMessage.length; + + return fullMessage.substr(boilerplateEnd); + }; + + Spec.pendingSpecExceptionMessage = '=> marked Pending'; Spec.isPendingSpecException = function(e) { - return e.toString().indexOf(Spec.pendingSpecExceptionMessage) !== -1; + return !!(e && e.toString && e.toString().indexOf(Spec.pendingSpecExceptionMessage) !== -1); }; return Spec; }; -if (typeof window == void 0 && typeof exports == "object") { +if (typeof window == void 0 && typeof exports == 'object') { exports.Spec = jasmineRequire.Spec; } +/*jshint bitwise: false*/ + +getJasmineRequireObj().Order = function() { + function Order(options) { + this.random = 'random' in options ? options.random : true; + var seed = this.seed = options.seed || generateSeed(); + this.sort = this.random ? randomOrder : naturalOrder; + + function naturalOrder(items) { + return items; + } + + function randomOrder(items) { + var copy = items.slice(); + copy.sort(function(a, b) { + return jenkinsHash(seed + a.id) - jenkinsHash(seed + b.id); + }); + return copy; + } + + function generateSeed() { + return String(Math.random()).slice(-5); + } + + // Bob Jenkins One-at-a-Time Hash algorithm is a non-cryptographic hash function + // used to get a different output when the key changes slighly. + // We use your return to sort the children randomly in a consistent way when + // used in conjunction with a seed + + function jenkinsHash(key) { + var hash, i; + for(hash = i = 0; i < key.length; ++i) { + hash += key.charCodeAt(i); + hash += (hash << 10); + hash ^= (hash >> 6); + } + hash += (hash << 3); + hash ^= (hash >> 11); + hash += (hash << 15); + return hash; + } + + } + + return Order; +}; + getJasmineRequireObj().Env = function(j$) { function Env(options) { options = options || {}; @@ -389,33 +513,54 @@ getJasmineRequireObj().Env = function(j$) { var realSetTimeout = j$.getGlobal().setTimeout; var realClearTimeout = j$.getGlobal().clearTimeout; - this.clock = new j$.Clock(global, new j$.DelayedFunctionScheduler()); + this.clock = new j$.Clock(global, function () { return new j$.DelayedFunctionScheduler(); }, new j$.MockDate(global)); var runnableLookupTable = {}; - - var spies = []; + var runnableResources = {}; var currentSpec = null; - var currentSuite = null; + var currentlyExecutingSuites = []; + var currentDeclarationSuite = null; + var throwOnExpectationFailure = false; + var random = false; + var seed = null; + + var currentSuite = function() { + return currentlyExecutingSuites[currentlyExecutingSuites.length - 1]; + }; + + var currentRunnable = function() { + return currentSpec || currentSuite(); + }; var reporter = new j$.ReportDispatcher([ - "jasmineStarted", - "jasmineDone", - "suiteStarted", - "suiteDone", - "specStarted", - "specDone" + 'jasmineStarted', + 'jasmineDone', + 'suiteStarted', + 'suiteDone', + 'specStarted', + 'specDone' ]); this.specFilter = function() { return true; }; - var equalityTesters = []; - - var customEqualityTesters = []; this.addCustomEqualityTester = function(tester) { - customEqualityTesters.push(tester); + if(!currentRunnable()) { + throw new Error('Custom Equalities must be added in a before function or a spec'); + } + runnableResources[currentRunnable().id].customEqualityTesters.push(tester); + }; + + this.addMatchers = function(matchersToAdd) { + if(!currentRunnable()) { + throw new Error('Matchers must be added in a before function or a spec'); + } + var customMatchers = runnableResources[currentRunnable().id].customMatchers; + for (var matcherName in matchersToAdd) { + customMatchers[matcherName] = matchersToAdd[matcherName]; + } }; j$.Expectation.addCoreMatchers(j$.matchers); @@ -433,7 +578,8 @@ getJasmineRequireObj().Env = function(j$) { var expectationFactory = function(actual, spec) { return j$.Expectation.Factory({ util: j$.matchersUtil, - customEqualityTesters: customEqualityTesters, + customEqualityTesters: runnableResources[spec.id].customEqualityTesters, + customMatchers: runnableResources[spec.id].customMatchers, actual: actual, addExpectationResult: addExpectationResult }); @@ -443,30 +589,38 @@ getJasmineRequireObj().Env = function(j$) { } }; - var specStarted = function(spec) { - currentSpec = spec; - reporter.specStarted(spec.result); + var defaultResourcesForRunnable = function(id, parentRunnableId) { + var resources = {spies: [], customEqualityTesters: [], customMatchers: {}}; + + if(runnableResources[parentRunnableId]){ + resources.customEqualityTesters = j$.util.clone(runnableResources[parentRunnableId].customEqualityTesters); + resources.customMatchers = j$.util.clone(runnableResources[parentRunnableId].customMatchers); + } + + runnableResources[id] = resources; }; - var beforeFns = function(suite) { + var clearResourcesForRunnable = function(id) { + spyRegistry.clearSpies(); + delete runnableResources[id]; + }; + + var beforeAndAfterFns = function(suite) { return function() { - var befores = []; + var befores = [], + afters = []; + while(suite) { befores = befores.concat(suite.beforeFns); - suite = suite.parentSuite; - } - return befores.reverse(); - }; - }; - - var afterFns = function(suite) { - return function() { - var afters = []; - while(suite) { afters = afters.concat(suite.afterFns); + suite = suite.parentSuite; } - return afters; + + return { + befores: befores.reverse(), + afters: afters + }; }; }; @@ -476,13 +630,13 @@ getJasmineRequireObj().Env = function(j$) { // TODO: we may just be able to pass in the fn instead of wrapping here var buildExpectationResult = j$.buildExpectationResult, - exceptionFormatter = new j$.ExceptionFormatter(), - expectationResultFactory = function(attrs) { - attrs.messageFormatter = exceptionFormatter.message; - attrs.stackFormatter = exceptionFormatter.stack; + exceptionFormatter = new j$.ExceptionFormatter(), + expectationResultFactory = function(attrs) { + attrs.messageFormatter = exceptionFormatter.message; + attrs.stackFormatter = exceptionFormatter.stack; - return buildExpectationResult(attrs); - }; + return buildExpectationResult(attrs); + }; // TODO: fix this naming, and here's where the value comes in this.catchExceptions = function(value) { @@ -511,9 +665,34 @@ getJasmineRequireObj().Env = function(j$) { return j$.Spec.isPendingSpecException(e) || catchExceptions; }; + this.throwOnExpectationFailure = function(value) { + throwOnExpectationFailure = !!value; + }; + + this.throwingExpectationFailures = function() { + return throwOnExpectationFailure; + }; + + this.randomizeTests = function(value) { + random = !!value; + }; + + this.randomTests = function() { + return random; + }; + + this.seed = function(value) { + if (value) { + seed = value; + } + return seed; + }; + var queueRunnerFactory = function(options) { options.catchException = catchException; options.clearStack = options.clearStack || clearStack; + options.timeout = {setTimeout: realSetTimeout, clearTimeout: realClearTimeout}; + options.fail = self.fail; new j$.QueueRunner(options).execute(); }; @@ -522,66 +701,79 @@ getJasmineRequireObj().Env = function(j$) { env: this, id: getNextSuiteId(), description: 'Jasmine__TopLevel__Suite', - queueRunner: queueRunnerFactory, - resultCallback: function() {} // TODO - hook this up + queueRunner: queueRunnerFactory }); runnableLookupTable[topSuite.id] = topSuite; - currentSuite = topSuite; + defaultResourcesForRunnable(topSuite.id); + currentDeclarationSuite = topSuite; this.topSuite = function() { return topSuite; }; this.execute = function(runnablesToRun) { - runnablesToRun = runnablesToRun || [topSuite.id]; + if(!runnablesToRun) { + if (focusedRunnables.length) { + runnablesToRun = focusedRunnables; + } else { + runnablesToRun = [topSuite.id]; + } + } - var allFns = []; - for(var i = 0; i < runnablesToRun.length; i++) { - var runnable = runnableLookupTable[runnablesToRun[i]]; - allFns.push((function(runnable) { return function(done) { runnable.execute(done); }; })(runnable)); + var order = new j$.Order({ + random: random, + seed: seed + }); + + var processor = new j$.TreeProcessor({ + tree: topSuite, + runnableIds: runnablesToRun, + queueRunnerFactory: queueRunnerFactory, + nodeStart: function(suite) { + currentlyExecutingSuites.push(suite); + defaultResourcesForRunnable(suite.id, suite.parentSuite.id); + reporter.suiteStarted(suite.result); + }, + nodeComplete: function(suite, result) { + if (!suite.disabled) { + clearResourcesForRunnable(suite.id); + } + currentlyExecutingSuites.pop(); + reporter.suiteDone(result); + }, + orderChildren: function(node) { + return order.sort(node.children); + } + }); + + if(!processor.processTree().valid) { + throw new Error('Invalid order: would cause a beforeAll or afterAll to be run multiple times'); } reporter.jasmineStarted({ totalSpecsDefined: totalSpecsDefined }); - queueRunnerFactory({fns: allFns, onComplete: reporter.jasmineDone}); + processor.execute(function() { + reporter.jasmineDone({ + order: order + }); + }); }; this.addReporter = function(reporterToAdd) { reporter.addReporter(reporterToAdd); }; - this.addMatchers = function(matchersToAdd) { - j$.Expectation.addMatchers(matchersToAdd); - }; - - this.spyOn = function(obj, methodName) { - if (j$.util.isUndefined(obj)) { - throw new Error("spyOn could not find an object to spy upon for " + methodName + "()"); + var spyRegistry = new j$.SpyRegistry({currentSpies: function() { + if(!currentRunnable()) { + throw new Error('Spies must be created in a before function or a spec'); } + return runnableResources[currentRunnable().id].spies; + }}); - if (j$.util.isUndefined(obj[methodName])) { - throw new Error(methodName + '() method does not exist'); - } - - if (obj[methodName] && j$.isSpy(obj[methodName])) { - //TODO?: should this return the current spy? Downside: may cause user confusion about spy state - throw new Error(methodName + ' has already been spied upon'); - } - - var spy = j$.createSpy(methodName, obj[methodName]); - - spies.push({ - spy: spy, - baseObj: obj, - methodName: methodName, - originalValue: obj[methodName] - }); - - obj[methodName] = spy; - - return spy; + this.spyOn = function() { + return spyRegistry.spyOn.apply(spyRegistry, arguments); }; var suiteFactory = function(description) { @@ -589,12 +781,10 @@ getJasmineRequireObj().Env = function(j$) { env: self, id: getNextSuiteId(), description: description, - parentSuite: currentSuite, - queueRunner: queueRunnerFactory, - onStart: suiteStarted, - resultCallback: function(attrs) { - reporter.suiteDone(attrs); - } + parentSuite: currentDeclarationSuite, + expectationFactory: expectationFactory, + expectationResultFactory: expectationResultFactory, + throwOnExpectationFailure: throwOnExpectationFailure }); runnableLookupTable[suite.id] = suite; @@ -603,10 +793,40 @@ getJasmineRequireObj().Env = function(j$) { this.describe = function(description, specDefinitions) { var suite = suiteFactory(description); + if (specDefinitions.length > 0) { + throw new Error('describe does not expect a done parameter'); + } + if (currentDeclarationSuite.markedPending) { + suite.pend(); + } + addSpecsToSuite(suite, specDefinitions); + return suite; + }; - var parentSuite = currentSuite; + this.xdescribe = function(description, specDefinitions) { + var suite = suiteFactory(description); + suite.pend(); + addSpecsToSuite(suite, specDefinitions); + return suite; + }; + + var focusedRunnables = []; + + this.fdescribe = function(description, specDefinitions) { + var suite = suiteFactory(description); + suite.isFocused = true; + + focusedRunnables.push(suite.id); + unfocusAncestor(); + addSpecsToSuite(suite, specDefinitions); + + return suite; + }; + + function addSpecsToSuite(suite, specDefinitions) { + var parentSuite = currentDeclarationSuite; parentSuite.addChild(suite); - currentSuite = suite; + currentDeclarationSuite = suite; var declarationError = null; try { @@ -616,31 +836,43 @@ getJasmineRequireObj().Env = function(j$) { } if (declarationError) { - this.it("encountered a declaration exception", function() { + self.it('encountered a declaration exception', function() { throw declarationError; }); } - currentSuite = parentSuite; + currentDeclarationSuite = parentSuite; + } - return suite; - }; + function findFocusedAncestor(suite) { + while (suite) { + if (suite.isFocused) { + return suite.id; + } + suite = suite.parentSuite; + } - this.xdescribe = function(description, specDefinitions) { - var suite = this.describe(description, specDefinitions); - suite.disable(); - return suite; - }; + return null; + } - var specFactory = function(description, fn, suite) { + function unfocusAncestor() { + var focusedAncestor = findFocusedAncestor(currentDeclarationSuite); + if (focusedAncestor) { + for (var i = 0; i < focusedRunnables.length; i++) { + if (focusedRunnables[i] === focusedAncestor) { + focusedRunnables.splice(i, 1); + break; + } + } + } + } + + var specFactory = function(description, fn, suite, timeout) { totalSpecsDefined++; - var spec = new j$.Spec({ id: getNextSpecId(), - beforeFns: beforeFns(suite), - afterFns: afterFns(suite), + beforeAndAfterFns: beforeAndAfterFns(suite), expectationFactory: expectationFactory, - exceptionFormatter: exceptionFormatter, resultCallback: specResultCallback, getSpecName: function(spec) { return getSpecName(spec, suite); @@ -649,8 +881,12 @@ getJasmineRequireObj().Env = function(j$) { description: description, expectationResultFactory: expectationResultFactory, queueRunnerFactory: queueRunnerFactory, - fn: fn, - timer: {setTimeout: realSetTimeout, clearTimeout: realClearTimeout} + userContext: function() { return suite.clonedSharedUserContext(); }, + queueableFn: { + fn: fn, + timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; } + }, + throwOnExpectationFailure: throwOnExpectationFailure }); runnableLookupTable[spec.id] = spec; @@ -661,53 +897,101 @@ getJasmineRequireObj().Env = function(j$) { return spec; - function removeAllSpies() { - for (var i = 0; i < spies.length; i++) { - var spyEntry = spies[i]; - spyEntry.baseObj[spyEntry.methodName] = spyEntry.originalValue; - } - spies = []; - } - function specResultCallback(result) { - removeAllSpies(); - j$.Expectation.resetMatchers(); - customEqualityTesters = []; + clearResourcesForRunnable(spec.id); currentSpec = null; reporter.specDone(result); } + + function specStarted(spec) { + currentSpec = spec; + defaultResourcesForRunnable(spec.id, suite.id); + reporter.specStarted(spec.result); + } }; - var suiteStarted = function(suite) { - reporter.suiteStarted(suite.result); - }; - - this.it = function(description, fn) { - var spec = specFactory(description, fn, currentSuite); - currentSuite.addChild(spec); + this.it = function(description, fn, timeout) { + var spec = specFactory(description, fn, currentDeclarationSuite, timeout); + if (currentDeclarationSuite.markedPending) { + spec.pend(); + } + currentDeclarationSuite.addChild(spec); return spec; }; - this.xit = function(description, fn) { - var spec = this.it(description, fn); - spec.pend(); + this.xit = function() { + var spec = this.it.apply(this, arguments); + spec.pend('Temporarily disabled with xit'); + return spec; + }; + + this.fit = function(description, fn, timeout){ + var spec = specFactory(description, fn, currentDeclarationSuite, timeout); + currentDeclarationSuite.addChild(spec); + focusedRunnables.push(spec.id); + unfocusAncestor(); return spec; }; this.expect = function(actual) { - return currentSpec.expect(actual); + if (!currentRunnable()) { + throw new Error('\'expect\' was used when there was no current spec, this could be because an asynchronous test timed out'); + } + + return currentRunnable().expect(actual); }; - this.beforeEach = function(beforeEachFunction) { - currentSuite.beforeEach(beforeEachFunction); + this.beforeEach = function(beforeEachFunction, timeout) { + currentDeclarationSuite.beforeEach({ + fn: beforeEachFunction, + timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; } + }); }; - this.afterEach = function(afterEachFunction) { - currentSuite.afterEach(afterEachFunction); + this.beforeAll = function(beforeAllFunction, timeout) { + currentDeclarationSuite.beforeAll({ + fn: beforeAllFunction, + timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; } + }); }; - this.pending = function() { - throw j$.Spec.pendingSpecExceptionMessage; + this.afterEach = function(afterEachFunction, timeout) { + currentDeclarationSuite.afterEach({ + fn: afterEachFunction, + timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; } + }); + }; + + this.afterAll = function(afterAllFunction, timeout) { + currentDeclarationSuite.afterAll({ + fn: afterAllFunction, + timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; } + }); + }; + + this.pending = function(message) { + var fullMessage = j$.Spec.pendingSpecExceptionMessage; + if(message) { + fullMessage += message; + } + throw fullMessage; + }; + + this.fail = function(error) { + var message = 'Failed'; + if (error) { + message += ': '; + message += error.message || error; + } + + currentRunnable().addExpectationResult(false, { + matcherName: '', + passed: false, + expected: '', + actual: '', + message: message, + error: error && error.message ? error : null + }); }; } @@ -723,10 +1007,11 @@ getJasmineRequireObj().JsApiReporter = function() { function JsApiReporter(options) { var timer = options.timer || noopTimer, - status = "loaded"; + status = 'loaded'; this.started = false; this.finished = false; + this.runDetails = {}; this.jasmineStarted = function() { this.started = true; @@ -736,8 +1021,9 @@ getJasmineRequireObj().JsApiReporter = function() { var executionTime; - this.jasmineDone = function() { + this.jasmineDone = function(runDetails) { this.finished = true; + this.runDetails = runDetails; executionTime = timer.elapsed(); status = 'done'; }; @@ -746,26 +1032,31 @@ getJasmineRequireObj().JsApiReporter = function() { return status; }; - var suites = {}; + var suites = [], + suites_hash = {}; this.suiteStarted = function(result) { - storeSuite(result); + suites_hash[result.id] = result; }; this.suiteDone = function(result) { storeSuite(result); }; + this.suiteResults = function(index, length) { + return suites.slice(index, index + length); + }; + function storeSuite(result) { - suites[result.id] = result; + suites.push(result); + suites_hash[result.id] = result; } this.suites = function() { - return suites; + return suites_hash; }; var specs = []; - this.specStarted = function(result) { }; this.specDone = function(result) { specs.push(result); @@ -788,43 +1079,6 @@ getJasmineRequireObj().JsApiReporter = function() { return JsApiReporter; }; -getJasmineRequireObj().Any = function() { - - function Any(expectedObject) { - this.expectedObject = expectedObject; - } - - Any.prototype.jasmineMatches = function(other) { - if (this.expectedObject == String) { - return typeof other == 'string' || other instanceof String; - } - - if (this.expectedObject == Number) { - return typeof other == 'number' || other instanceof Number; - } - - if (this.expectedObject == Function) { - return typeof other == 'function' || other instanceof Function; - } - - if (this.expectedObject == Object) { - return typeof other == 'object'; - } - - if (this.expectedObject == Boolean) { - return typeof other == 'boolean'; - } - - return other instanceof this.expectedObject; - }; - - Any.prototype.jasmineToString = function() { - return ''; - }; - - return Any; -}; - getJasmineRequireObj().CallTracker = function() { function CallTracker() { @@ -877,7 +1131,7 @@ getJasmineRequireObj().CallTracker = function() { }; getJasmineRequireObj().Clock = function() { - function Clock(global, delayedFunctionScheduler) { + function Clock(global, delayedFunctionSchedulerFactory, mockDate) { var self = this, realTimingFunctions = { setTimeout: global.setTimeout, @@ -892,25 +1146,48 @@ getJasmineRequireObj().Clock = function() { clearInterval: clearInterval }, installed = false, + delayedFunctionScheduler, timer; + self.install = function() { + if(!originalTimingFunctionsIntact()) { + throw new Error('Jasmine Clock was unable to install over custom global timer functions. Is the clock already installed?'); + } replace(global, fakeTimingFunctions); timer = fakeTimingFunctions; + delayedFunctionScheduler = delayedFunctionSchedulerFactory(); installed = true; + + return self; }; self.uninstall = function() { - delayedFunctionScheduler.reset(); + delayedFunctionScheduler = null; + mockDate.uninstall(); replace(global, realTimingFunctions); + timer = realTimingFunctions; installed = false; }; + self.withMock = function(closure) { + this.install(); + try { + closure(); + } finally { + this.uninstall(); + } + }; + + self.mockDate = function(initialDate) { + mockDate.install(initialDate); + }; + self.setTimeout = function(fn, delay, params) { if (legacyIE()) { if (arguments.length > 2) { - throw new Error("IE < 9 cannot support extra params to setTimeout without a polyfill"); + throw new Error('IE < 9 cannot support extra params to setTimeout without a polyfill'); } return timer.setTimeout(fn, delay); } @@ -920,7 +1197,7 @@ getJasmineRequireObj().Clock = function() { self.setInterval = function(fn, delay, params) { if (legacyIE()) { if (arguments.length > 2) { - throw new Error("IE < 9 cannot support extra params to setInterval without a polyfill"); + throw new Error('IE < 9 cannot support extra params to setInterval without a polyfill'); } return timer.setInterval(fn, delay); } @@ -937,14 +1214,21 @@ getJasmineRequireObj().Clock = function() { self.tick = function(millis) { if (installed) { - delayedFunctionScheduler.tick(millis); + delayedFunctionScheduler.tick(millis, function(millis) { mockDate.tick(millis); }); } else { - throw new Error("Mock clock is not installed, use jasmine.clock().install()"); + throw new Error('Mock clock is not installed, use jasmine.clock().install()'); } }; return self; + function originalTimingFunctionsIntact() { + return global.setTimeout === realTimingFunctions.setTimeout && + global.clearTimeout === realTimingFunctions.clearTimeout && + global.setInterval === realTimingFunctions.setInterval && + global.clearInterval === realTimingFunctions.clearInterval; + } + function legacyIE() { //if these methods are polyfilled, apply will be present return !(realTimingFunctions.setTimeout || realTimingFunctions.setInterval).apply; @@ -973,7 +1257,7 @@ getJasmineRequireObj().Clock = function() { } function argSlice(argsObj, n) { - return Array.prototype.slice.call(argsObj, 2); + return Array.prototype.slice.call(argsObj, n); } } @@ -988,11 +1272,11 @@ getJasmineRequireObj().DelayedFunctionScheduler = function() { var currentTime = 0; var delayedFnCount = 0; - self.tick = function(millis) { + self.tick = function(millis, tickDate) { millis = millis || 0; var endTime = currentTime + millis; - runScheduledFunctions(endTime); + runScheduledFunctions(endTime, tickDate); currentTime = endTime; }; @@ -1054,13 +1338,6 @@ getJasmineRequireObj().DelayedFunctionScheduler = function() { } }; - self.reset = function() { - currentTime = 0; - scheduledLookup = []; - scheduledFunctions = {}; - delayedFnCount = 0; - }; - return self; function indexOfFirstToPass(array, testFn) { @@ -1096,30 +1373,42 @@ getJasmineRequireObj().DelayedFunctionScheduler = function() { scheduledFn.runAtMillis + scheduledFn.millis); } - function runScheduledFunctions(endTime) { + function forEachFunction(funcsToRun, callback) { + for (var i = 0; i < funcsToRun.length; ++i) { + callback(funcsToRun[i]); + } + } + + function runScheduledFunctions(endTime, tickDate) { + tickDate = tickDate || function() {}; if (scheduledLookup.length === 0 || scheduledLookup[0] > endTime) { + tickDate(endTime); return; } do { - currentTime = scheduledLookup.shift(); + var newCurrentTime = scheduledLookup.shift(); + tickDate(newCurrentTime - currentTime); + + currentTime = newCurrentTime; var funcsToRun = scheduledFunctions[currentTime]; delete scheduledFunctions[currentTime]; - for (var i = 0; i < funcsToRun.length; ++i) { - var funcToRun = funcsToRun[i]; - funcToRun.funcToCall.apply(null, funcToRun.params || []); - + forEachFunction(funcsToRun, function(funcToRun) { if (funcToRun.recurring) { reschedule(funcToRun); } - } + }); + + forEachFunction(funcsToRun, function(funcToRun) { + funcToRun.funcToCall.apply(null, funcToRun.params || []); + }); } while (scheduledLookup.length > 0 && - // checking first if we're out of time prevents setTimeout(0) - // scheduled in a funcToRun from forcing an extra iteration - currentTime !== endTime && - scheduledLookup[0] <= endTime); + // checking first if we're out of time prevents setTimeout(0) + // scheduled in a funcToRun from forcing an extra iteration + currentTime !== endTime && + scheduledLookup[0] <= endTime); } } @@ -1129,16 +1418,20 @@ getJasmineRequireObj().DelayedFunctionScheduler = function() { getJasmineRequireObj().ExceptionFormatter = function() { function ExceptionFormatter() { this.message = function(error) { - var message = error.name + - ': ' + - error.message; + var message = ''; + + if (error.name && error.message) { + message += error.name + ': ' + error.message; + } else { + message += error.toString() + ' thrown'; + } if (error.fileName || error.sourceURL) { - message += " in " + (error.fileName || error.sourceURL); + message += ' in ' + (error.fileName || error.sourceURL); } if (error.line || error.lineNumber) { - message += " (line " + (error.line || error.lineNumber) + ")"; + message += ' (line ' + (error.line || error.lineNumber) + ')'; } return message; @@ -1154,8 +1447,6 @@ getJasmineRequireObj().ExceptionFormatter = function() { getJasmineRequireObj().Expectation = function() { - var matchers = {}; - function Expectation(options) { this.util = options.util || { buildFailureMessage: function() {} }; this.customEqualityTesters = options.customEqualityTesters || []; @@ -1163,8 +1454,9 @@ getJasmineRequireObj().Expectation = function() { this.addExpectationResult = options.addExpectationResult || function(){}; this.isNot = options.isNot; - for (var matcherName in matchers) { - this[matcherName] = matchers[matcherName]; + var customMatchers = options.customMatchers || {}; + for (var matcherName in customMatchers) { + this[matcherName] = Expectation.prototype.wrapCompare(matcherName, customMatchers[matcherName]); } } @@ -1172,12 +1464,12 @@ getJasmineRequireObj().Expectation = function() { return function() { var args = Array.prototype.slice.call(arguments, 0), expected = args.slice(0), - message = ""; + message = ''; args.unshift(this.actual); var matcher = matcherFactory(this.util, this.customEqualityTesters), - matcherCompare = matcher.compare; + matcherCompare = matcher.compare; function defaultNegativeCompare() { var result = matcher.compare.apply(null, args); @@ -1197,7 +1489,11 @@ getJasmineRequireObj().Expectation = function() { args.unshift(name); message = this.util.buildFailureMessage.apply(null, args); } else { - message = result.message; + if (Object.prototype.toString.apply(result.message) === '[object Function]') { + message = result.message(); + } else { + message = result.message; + } } } @@ -1227,19 +1523,6 @@ getJasmineRequireObj().Expectation = function() { } }; - Expectation.addMatchers = function(matchersToAdd) { - for (var name in matchersToAdd) { - var matcher = matchersToAdd[name]; - matchers[name] = Expectation.prototype.wrapCompare(name, matcher); - } - }; - - Expectation.resetMatchers = function() { - for (var name in matchers) { - delete matchers[name]; - } - }; - Expectation.Factory = function(options) { options = options || {}; @@ -1262,29 +1545,34 @@ getJasmineRequireObj().buildExpectationResult = function() { var messageFormatter = options.messageFormatter || function() {}, stackFormatter = options.stackFormatter || function() {}; - return { + var result = { matcherName: options.matcherName, - expected: options.expected, - actual: options.actual, message: message(), stack: stack(), passed: options.passed }; + if(!result.passed) { + result.expected = options.expected; + result.actual = options.actual; + } + + return result; + function message() { if (options.passed) { - return "Passed."; + return 'Passed.'; } else if (options.message) { return options.message; } else if (options.error) { return messageFormatter(options.error); } - return ""; + return ''; } function stack() { if (options.passed) { - return ""; + return ''; } var error = options.error; @@ -1302,45 +1590,94 @@ getJasmineRequireObj().buildExpectationResult = function() { return buildExpectationResult; }; -getJasmineRequireObj().ObjectContaining = function(j$) { +getJasmineRequireObj().MockDate = function() { + function MockDate(global) { + var self = this; + var currentTime = 0; - function ObjectContaining(sample) { - this.sample = sample; - } + if (!global || !global.Date) { + self.install = function() {}; + self.tick = function() {}; + self.uninstall = function() {}; + return self; + } - ObjectContaining.prototype.jasmineMatches = function(other, mismatchKeys, mismatchValues) { - if (typeof(this.sample) !== "object") { throw new Error("You must provide an object to objectContaining, not '"+this.sample+"'."); } + var GlobalDate = global.Date; - mismatchKeys = mismatchKeys || []; - mismatchValues = mismatchValues || []; + self.install = function(mockDate) { + if (mockDate instanceof GlobalDate) { + currentTime = mockDate.getTime(); + } else { + currentTime = new GlobalDate().getTime(); + } - var hasKey = function(obj, keyName) { - return obj !== null && !j$.util.isUndefined(obj[keyName]); + global.Date = FakeDate; }; - for (var property in this.sample) { - if (!hasKey(other, property) && hasKey(this.sample, property)) { - mismatchKeys.push("expected has key '" + property + "', but missing from actual."); - } - else if (!j$.matchersUtil.equals(this.sample[property], other[property])) { - mismatchValues.push("'" + property + "' was '" + (other[property] ? j$.util.htmlEscape(other[property].toString()) : other[property]) + "' in actual, but was '" + (this.sample[property] ? j$.util.htmlEscape(this.sample[property].toString()) : this.sample[property]) + "' in expected."); + self.tick = function(millis) { + millis = millis || 0; + currentTime = currentTime + millis; + }; + + self.uninstall = function() { + currentTime = 0; + global.Date = GlobalDate; + }; + + createDateProperties(); + + return self; + + function FakeDate() { + switch(arguments.length) { + case 0: + return new GlobalDate(currentTime); + case 1: + return new GlobalDate(arguments[0]); + case 2: + return new GlobalDate(arguments[0], arguments[1]); + case 3: + return new GlobalDate(arguments[0], arguments[1], arguments[2]); + case 4: + return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3]); + case 5: + return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3], + arguments[4]); + case 6: + return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3], + arguments[4], arguments[5]); + default: + return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3], + arguments[4], arguments[5], arguments[6]); } } - return (mismatchKeys.length === 0 && mismatchValues.length === 0); - }; + function createDateProperties() { + FakeDate.prototype = GlobalDate.prototype; - ObjectContaining.prototype.jasmineToString = function() { - return ""; - }; + FakeDate.now = function() { + if (GlobalDate.now) { + return currentTime; + } else { + throw new Error('Browser does not support Date.now()'); + } + }; - return ObjectContaining; + FakeDate.toSource = GlobalDate.toSource; + FakeDate.toString = GlobalDate.toString; + FakeDate.parse = GlobalDate.parse; + FakeDate.UTC = GlobalDate.UTC; + } + } + + return MockDate; }; getJasmineRequireObj().pp = function(j$) { function PrettyPrinter() { this.ppNestLevel_ = 0; + this.seen = []; } PrettyPrinter.prototype.format = function(value) { @@ -1350,6 +1687,8 @@ getJasmineRequireObj().pp = function(j$) { this.emitScalar('undefined'); } else if (value === null) { this.emitScalar('null'); + } else if (value === 0 && 1/value === -Infinity) { + this.emitScalar('-0'); } else if (value === j$.getGlobal()) { this.emitScalar(''); } else if (value.jasmineToString) { @@ -1357,7 +1696,7 @@ getJasmineRequireObj().pp = function(j$) { } else if (typeof value === 'string') { this.emitString(value); } else if (j$.isSpy(value)) { - this.emitScalar("spy on " + value.and.identity()); + this.emitScalar('spy on ' + value.and.identity()); } else if (value instanceof RegExp) { this.emitScalar(value.toString()); } else if (typeof value === 'function') { @@ -1366,16 +1705,18 @@ getJasmineRequireObj().pp = function(j$) { this.emitScalar('HTMLNode'); } else if (value instanceof Date) { this.emitScalar('Date(' + value + ')'); - } else if (value.__Jasmine_been_here_before__) { + } else if (value.toString && typeof value === 'object' && !(value instanceof Array) && value.toString !== Object.prototype.toString) { + this.emitScalar(value.toString()); + } else if (j$.util.arrayContains(this.seen, value)) { this.emitScalar(''); } else if (j$.isArray_(value) || j$.isA_('Object', value)) { - value.__Jasmine_been_here_before__ = true; + this.seen.push(value); if (j$.isArray_(value)) { this.emitArray(value); } else { this.emitObject(value); } - delete value.__Jasmine_been_here_before__; + this.seen.pop(); } else { this.emitScalar(value.toString()); } @@ -1386,10 +1727,9 @@ getJasmineRequireObj().pp = function(j$) { PrettyPrinter.prototype.iterateObject = function(obj, fn) { for (var property in obj) { - if (!obj.hasOwnProperty(property)) { continue; } - if (property == '__Jasmine_been_here_before__') { continue; } + if (!Object.prototype.hasOwnProperty.call(obj, property)) { continue; } fn(property, obj.__lookupGetter__ ? (!j$.util.isUndefined(obj.__lookupGetter__(property)) && - obj.__lookupGetter__(property) !== null) : false); + obj.__lookupGetter__(property) !== null) : false); } }; @@ -1411,33 +1751,55 @@ getJasmineRequireObj().pp = function(j$) { }; StringPrettyPrinter.prototype.emitString = function(value) { - this.append("'" + value + "'"); + this.append('\'' + value + '\''); }; StringPrettyPrinter.prototype.emitArray = function(array) { if (this.ppNestLevel_ > j$.MAX_PRETTY_PRINT_DEPTH) { - this.append("Array"); + this.append('Array'); return; } - + var length = Math.min(array.length, j$.MAX_PRETTY_PRINT_ARRAY_LENGTH); this.append('[ '); - for (var i = 0; i < array.length; i++) { + for (var i = 0; i < length; i++) { if (i > 0) { this.append(', '); } this.format(array[i]); } + if(array.length > length){ + this.append(', ...'); + } + + var self = this; + var first = array.length === 0; + this.iterateObject(array, function(property, isGetter) { + if (property.match(/^\d+$/)) { + return; + } + + if (first) { + first = false; + } else { + self.append(', '); + } + + self.formatProperty(array, property, isGetter); + }); + this.append(' ]'); }; StringPrettyPrinter.prototype.emitObject = function(obj) { + var constructorName = obj.constructor ? j$.fnNameFor(obj.constructor) : 'null'; + this.append(constructorName); + if (this.ppNestLevel_ > j$.MAX_PRETTY_PRINT_DEPTH) { - this.append("Object"); return; } var self = this; - this.append('{ '); + this.append('({ '); var first = true; this.iterateObject(obj, function(property, isGetter) { @@ -1447,16 +1809,20 @@ getJasmineRequireObj().pp = function(j$) { self.append(', '); } - self.append(property); - self.append(' : '); - if (isGetter) { - self.append(''); - } else { - self.format(obj[property]); - } + self.formatProperty(obj, property, isGetter); }); - this.append(' }'); + this.append(' })'); + }; + + StringPrettyPrinter.prototype.formatProperty = function(obj, property, isGetter) { + this.append(property); + this.append(': '); + if (isGetter) { + this.append(''); + } else { + this.format(obj[property]); + } }; StringPrettyPrinter.prototype.append = function(value) { @@ -1470,32 +1836,46 @@ getJasmineRequireObj().pp = function(j$) { }; }; -getJasmineRequireObj().QueueRunner = function() { +getJasmineRequireObj().QueueRunner = function(j$) { + + function once(fn) { + var called = false; + return function() { + if (!called) { + called = true; + fn(); + } + }; + } function QueueRunner(attrs) { - this.fns = attrs.fns || []; + this.queueableFns = attrs.queueableFns || []; this.onComplete = attrs.onComplete || function() {}; this.clearStack = attrs.clearStack || function(fn) {fn();}; this.onException = attrs.onException || function() {}; this.catchException = attrs.catchException || function() { return true; }; - this.userContext = {}; + this.userContext = attrs.userContext || {}; + this.timeout = attrs.timeout || {setTimeout: setTimeout, clearTimeout: clearTimeout}; + this.fail = attrs.fail || function() {}; } QueueRunner.prototype.execute = function() { - this.run(this.fns, 0); + this.run(this.queueableFns, 0); }; - QueueRunner.prototype.run = function(fns, recursiveIndex) { - var length = fns.length, - self = this, - iterativeIndex; + QueueRunner.prototype.run = function(queueableFns, recursiveIndex) { + var length = queueableFns.length, + self = this, + iterativeIndex; + for(iterativeIndex = recursiveIndex; iterativeIndex < length; iterativeIndex++) { - var fn = fns[iterativeIndex]; - if (fn.length > 0) { - return attemptAsync(fn); + var queueableFn = queueableFns[iterativeIndex]; + if (queueableFn.fn.length > 0) { + attemptAsync(queueableFn); + return; } else { - attemptSync(fn); + attemptSync(queueableFn); } } @@ -1505,27 +1885,51 @@ getJasmineRequireObj().QueueRunner = function() { this.clearStack(this.onComplete); } - function attemptSync(fn) { + function attemptSync(queueableFn) { try { - fn.call(self.userContext); + queueableFn.fn.call(self.userContext); } catch (e) { - handleException(e); + handleException(e, queueableFn); } } - function attemptAsync(fn) { - var next = function () { self.run(fns, iterativeIndex + 1); }; + function attemptAsync(queueableFn) { + var clearTimeout = function () { + Function.prototype.apply.apply(self.timeout.clearTimeout, [j$.getGlobal(), [timeoutId]]); + }, + next = once(function () { + clearTimeout(timeoutId); + self.run(queueableFns, iterativeIndex + 1); + }), + timeoutId; + + next.fail = function() { + self.fail.apply(null, arguments); + next(); + }; + + if (queueableFn.timeout) { + timeoutId = Function.prototype.apply.apply(self.timeout.setTimeout, [j$.getGlobal(), [function() { + var error = new Error('Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.'); + onException(error); + next(); + }, queueableFn.timeout()]]); + } try { - fn.call(self.userContext, next); + queueableFn.fn.call(self.userContext, next); } catch (e) { - handleException(e); + handleException(e, queueableFn); next(); } } - function handleException(e) { + function onException(e) { self.onException(e); + } + + function handleException(e, queueableFn) { + onException(e); if (!self.catchException(e)) { //TODO: set a var when we catch an exception and //use a finally block to close the loop in a nice way.. @@ -1573,15 +1977,76 @@ getJasmineRequireObj().ReportDispatcher = function() { }; +getJasmineRequireObj().SpyRegistry = function(j$) { + + function SpyRegistry(options) { + options = options || {}; + var currentSpies = options.currentSpies || function() { return []; }; + + this.spyOn = function(obj, methodName) { + if (j$.util.isUndefined(obj)) { + throw new Error('spyOn could not find an object to spy upon for ' + methodName + '()'); + } + + if (j$.util.isUndefined(methodName)) { + throw new Error('No method name supplied'); + } + + if (j$.util.isUndefined(obj[methodName])) { + throw new Error(methodName + '() method does not exist'); + } + + if (obj[methodName] && j$.isSpy(obj[methodName])) { + //TODO?: should this return the current spy? Downside: may cause user confusion about spy state + throw new Error(methodName + ' has already been spied upon'); + } + + var descriptor; + try { + descriptor = Object.getOwnPropertyDescriptor(obj, methodName); + } catch(e) { + // IE 8 doesn't support `definePropery` on non-DOM nodes + } + + if (descriptor && !(descriptor.writable || descriptor.set)) { + throw new Error(methodName + ' is not declared writable or has no setter'); + } + + var spy = j$.createSpy(methodName, obj[methodName]); + + currentSpies().push({ + spy: spy, + baseObj: obj, + methodName: methodName, + originalValue: obj[methodName] + }); + + obj[methodName] = spy; + + return spy; + }; + + this.clearSpies = function() { + var spies = currentSpies(); + for (var i = 0; i < spies.length; i++) { + var spyEntry = spies[i]; + spyEntry.baseObj[spyEntry.methodName] = spyEntry.originalValue; + } + }; + } + + return SpyRegistry; +}; + getJasmineRequireObj().SpyStrategy = function() { function SpyStrategy(options) { options = options || {}; - var identity = options.name || "unknown", - originalFn = options.fn || function() {}, - getSpy = options.getSpy || function() {}, - plan = function() {}; + var identity = options.name || 'unknown', + originalFn = options.fn || function() {}, + getSpy = options.getSpy || function() {}, + plan = function() {}; this.identity = function() { return identity; @@ -1603,6 +2068,14 @@ getJasmineRequireObj().SpyStrategy = function() { return getSpy(); }; + this.returnValues = function() { + var values = Array.prototype.slice.call(arguments); + plan = function () { + return values.shift(); + }; + return getSpy(); + }; + this.throwError = function(something) { var error = (something instanceof Error) ? something : new Error(something); plan = function() { @@ -1625,31 +2098,36 @@ getJasmineRequireObj().SpyStrategy = function() { return SpyStrategy; }; -getJasmineRequireObj().Suite = function() { +getJasmineRequireObj().Suite = function(j$) { function Suite(attrs) { this.env = attrs.env; this.id = attrs.id; this.parentSuite = attrs.parentSuite; this.description = attrs.description; - this.onStart = attrs.onStart || function() {}; - this.resultCallback = attrs.resultCallback || function() {}; - this.clearStack = attrs.clearStack || function(fn) {fn();}; + this.expectationFactory = attrs.expectationFactory; + this.expectationResultFactory = attrs.expectationResultFactory; + this.throwOnExpectationFailure = !!attrs.throwOnExpectationFailure; this.beforeFns = []; this.afterFns = []; - this.queueRunner = attrs.queueRunner || function() {}; + this.beforeAllFns = []; + this.afterAllFns = []; this.disabled = false; this.children = []; this.result = { id: this.id, - status: this.disabled ? 'disabled' : '', description: this.description, - fullName: this.getFullName() + fullName: this.getFullName(), + failedExpectations: [] }; } + Suite.prototype.expect = function(actual) { + return this.expectationFactory(actual, this); + }; + Suite.prototype.getFullName = function() { var fullName = this.description; for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) { @@ -1664,64 +2142,148 @@ getJasmineRequireObj().Suite = function() { this.disabled = true; }; + Suite.prototype.pend = function(message) { + this.markedPending = true; + }; + Suite.prototype.beforeEach = function(fn) { this.beforeFns.unshift(fn); }; + Suite.prototype.beforeAll = function(fn) { + this.beforeAllFns.push(fn); + }; + Suite.prototype.afterEach = function(fn) { this.afterFns.unshift(fn); }; + Suite.prototype.afterAll = function(fn) { + this.afterAllFns.push(fn); + }; + Suite.prototype.addChild = function(child) { this.children.push(child); }; - Suite.prototype.execute = function(onComplete) { - var self = this; + Suite.prototype.status = function() { if (this.disabled) { - complete(); + return 'disabled'; + } + + if (this.markedPending) { + return 'pending'; + } + + if (this.result.failedExpectations.length > 0) { + return 'failed'; + } else { + return 'finished'; + } + }; + + Suite.prototype.isExecutable = function() { + return !this.disabled; + }; + + Suite.prototype.canBeReentered = function() { + return this.beforeAllFns.length === 0 && this.afterAllFns.length === 0; + }; + + Suite.prototype.getResult = function() { + this.result.status = this.status(); + return this.result; + }; + + Suite.prototype.sharedUserContext = function() { + if (!this.sharedContext) { + this.sharedContext = this.parentSuite ? clone(this.parentSuite.sharedUserContext()) : {}; + } + + return this.sharedContext; + }; + + Suite.prototype.clonedSharedUserContext = function() { + return clone(this.sharedUserContext()); + }; + + Suite.prototype.onException = function() { + if (arguments[0] instanceof j$.errors.ExpectationFailed) { return; } - var allFns = []; - - for (var i = 0; i < this.children.length; i++) { - allFns.push(wrapChildAsAsync(this.children[i])); + if(isAfterAll(this.children)) { + var data = { + matcherName: '', + passed: false, + expected: '', + actual: '', + error: arguments[0] + }; + this.result.failedExpectations.push(this.expectationResultFactory(data)); + } else { + for (var i = 0; i < this.children.length; i++) { + var child = this.children[i]; + child.onException.apply(child, arguments); + } } + }; - this.onStart(this); + Suite.prototype.addExpectationResult = function () { + if(isAfterAll(this.children) && isFailure(arguments)){ + var data = arguments[1]; + this.result.failedExpectations.push(this.expectationResultFactory(data)); + if(this.throwOnExpectationFailure) { + throw new j$.errors.ExpectationFailed(); + } + } else { + for (var i = 0; i < this.children.length; i++) { + var child = this.children[i]; + try { + child.addExpectationResult.apply(child, arguments); + } catch(e) { + // keep going + } + } + } + }; - this.queueRunner({ - fns: allFns, - onComplete: complete - }); + function isAfterAll(children) { + return children && children[0].result.status; + } - function complete() { - self.resultCallback(self.result); + function isFailure(args) { + return !args[0]; + } - if (onComplete) { - onComplete(); + function clone(obj) { + var clonedObj = {}; + for (var prop in obj) { + if (obj.hasOwnProperty(prop)) { + clonedObj[prop] = obj[prop]; } } - function wrapChildAsAsync(child) { - return function(done) { child.execute(done); }; - } - }; + return clonedObj; + } return Suite; }; -if (typeof window == void 0 && typeof exports == "object") { +if (typeof window == void 0 && typeof exports == 'object') { exports.Suite = jasmineRequire.Suite; } getJasmineRequireObj().Timer = function() { + var defaultNow = (function(Date) { + return function() { return new Date().getTime(); }; + })(Date); + function Timer(options) { options = options || {}; - var now = options.now || function() { return new Date().getTime(); }, - startTime; + var now = options.now || defaultNow, + startTime; this.start = function() { startTime = now(); @@ -1735,6 +2297,378 @@ getJasmineRequireObj().Timer = function() { return Timer; }; +getJasmineRequireObj().TreeProcessor = function() { + function TreeProcessor(attrs) { + var tree = attrs.tree, + runnableIds = attrs.runnableIds, + queueRunnerFactory = attrs.queueRunnerFactory, + nodeStart = attrs.nodeStart || function() {}, + nodeComplete = attrs.nodeComplete || function() {}, + orderChildren = attrs.orderChildren || function(node) { return node.children; }, + stats = { valid: true }, + processed = false, + defaultMin = Infinity, + defaultMax = 1 - Infinity; + + this.processTree = function() { + processNode(tree, false); + processed = true; + return stats; + }; + + this.execute = function(done) { + if (!processed) { + this.processTree(); + } + + if (!stats.valid) { + throw 'invalid order'; + } + + var childFns = wrapChildren(tree, 0); + + queueRunnerFactory({ + queueableFns: childFns, + userContext: tree.sharedUserContext(), + onException: function() { + tree.onException.apply(tree, arguments); + }, + onComplete: done + }); + }; + + function runnableIndex(id) { + for (var i = 0; i < runnableIds.length; i++) { + if (runnableIds[i] === id) { + return i; + } + } + } + + function processNode(node, parentEnabled) { + var executableIndex = runnableIndex(node.id); + + if (executableIndex !== undefined) { + parentEnabled = true; + } + + parentEnabled = parentEnabled && node.isExecutable(); + + if (!node.children) { + stats[node.id] = { + executable: parentEnabled && node.isExecutable(), + segments: [{ + index: 0, + owner: node, + nodes: [node], + min: startingMin(executableIndex), + max: startingMax(executableIndex) + }] + }; + } else { + var hasExecutableChild = false; + + var orderedChildren = orderChildren(node); + + for (var i = 0; i < orderedChildren.length; i++) { + var child = orderedChildren[i]; + + processNode(child, parentEnabled); + + if (!stats.valid) { + return; + } + + var childStats = stats[child.id]; + + hasExecutableChild = hasExecutableChild || childStats.executable; + } + + stats[node.id] = { + executable: hasExecutableChild + }; + + segmentChildren(node, orderedChildren, stats[node.id], executableIndex); + + if (!node.canBeReentered() && stats[node.id].segments.length > 1) { + stats = { valid: false }; + } + } + } + + function startingMin(executableIndex) { + return executableIndex === undefined ? defaultMin : executableIndex; + } + + function startingMax(executableIndex) { + return executableIndex === undefined ? defaultMax : executableIndex; + } + + function segmentChildren(node, orderedChildren, nodeStats, executableIndex) { + var currentSegment = { index: 0, owner: node, nodes: [], min: startingMin(executableIndex), max: startingMax(executableIndex) }, + result = [currentSegment], + lastMax = defaultMax, + orderedChildSegments = orderChildSegments(orderedChildren); + + function isSegmentBoundary(minIndex) { + return lastMax !== defaultMax && minIndex !== defaultMin && lastMax < minIndex - 1; + } + + for (var i = 0; i < orderedChildSegments.length; i++) { + var childSegment = orderedChildSegments[i], + maxIndex = childSegment.max, + minIndex = childSegment.min; + + if (isSegmentBoundary(minIndex)) { + currentSegment = {index: result.length, owner: node, nodes: [], min: defaultMin, max: defaultMax}; + result.push(currentSegment); + } + + currentSegment.nodes.push(childSegment); + currentSegment.min = Math.min(currentSegment.min, minIndex); + currentSegment.max = Math.max(currentSegment.max, maxIndex); + lastMax = maxIndex; + } + + nodeStats.segments = result; + } + + function orderChildSegments(children) { + var specifiedOrder = [], + unspecifiedOrder = []; + + for (var i = 0; i < children.length; i++) { + var child = children[i], + segments = stats[child.id].segments; + + for (var j = 0; j < segments.length; j++) { + var seg = segments[j]; + + if (seg.min === defaultMin) { + unspecifiedOrder.push(seg); + } else { + specifiedOrder.push(seg); + } + } + } + + specifiedOrder.sort(function(a, b) { + return a.min - b.min; + }); + + return specifiedOrder.concat(unspecifiedOrder); + } + + function executeNode(node, segmentNumber) { + if (node.children) { + return { + fn: function(done) { + nodeStart(node); + + queueRunnerFactory({ + onComplete: function() { + nodeComplete(node, node.getResult()); + done(); + }, + queueableFns: wrapChildren(node, segmentNumber), + userContext: node.sharedUserContext(), + onException: function() { + node.onException.apply(node, arguments); + } + }); + } + }; + } else { + return { + fn: function(done) { node.execute(done, stats[node.id].executable); } + }; + } + } + + function wrapChildren(node, segmentNumber) { + var result = [], + segmentChildren = stats[node.id].segments[segmentNumber].nodes; + + for (var i = 0; i < segmentChildren.length; i++) { + result.push(executeNode(segmentChildren[i].owner, segmentChildren[i].index)); + } + + if (!stats[node.id].executable) { + return result; + } + + return node.beforeAllFns.concat(result).concat(node.afterAllFns); + } + } + + return TreeProcessor; +}; + +getJasmineRequireObj().Any = function(j$) { + + function Any(expectedObject) { + if (typeof expectedObject === 'undefined') { + throw new TypeError( + 'jasmine.any() expects to be passed a constructor function. ' + + 'Please pass one or use jasmine.anything() to match any object.' + ); + } + this.expectedObject = expectedObject; + } + + Any.prototype.asymmetricMatch = function(other) { + if (this.expectedObject == String) { + return typeof other == 'string' || other instanceof String; + } + + if (this.expectedObject == Number) { + return typeof other == 'number' || other instanceof Number; + } + + if (this.expectedObject == Function) { + return typeof other == 'function' || other instanceof Function; + } + + if (this.expectedObject == Object) { + return typeof other == 'object'; + } + + if (this.expectedObject == Boolean) { + return typeof other == 'boolean'; + } + + return other instanceof this.expectedObject; + }; + + Any.prototype.jasmineToString = function() { + return ''; + }; + + return Any; +}; + +getJasmineRequireObj().Anything = function(j$) { + + function Anything() {} + + Anything.prototype.asymmetricMatch = function(other) { + return !j$.util.isUndefined(other) && other !== null; + }; + + Anything.prototype.jasmineToString = function() { + return ''; + }; + + return Anything; +}; + +getJasmineRequireObj().ArrayContaining = function(j$) { + function ArrayContaining(sample) { + this.sample = sample; + } + + ArrayContaining.prototype.asymmetricMatch = function(other) { + var className = Object.prototype.toString.call(this.sample); + if (className !== '[object Array]') { throw new Error('You must provide an array to arrayContaining, not \'' + this.sample + '\'.'); } + + for (var i = 0; i < this.sample.length; i++) { + var item = this.sample[i]; + if (!j$.matchersUtil.contains(other, item)) { + return false; + } + } + + return true; + }; + + ArrayContaining.prototype.jasmineToString = function () { + return ''; + }; + + return ArrayContaining; +}; + +getJasmineRequireObj().ObjectContaining = function(j$) { + + function ObjectContaining(sample) { + this.sample = sample; + } + + function getPrototype(obj) { + if (Object.getPrototypeOf) { + return Object.getPrototypeOf(obj); + } + + if (obj.constructor.prototype == obj) { + return null; + } + + return obj.constructor.prototype; + } + + function hasProperty(obj, property) { + if (!obj) { + return false; + } + + if (Object.prototype.hasOwnProperty.call(obj, property)) { + return true; + } + + return hasProperty(getPrototype(obj), property); + } + + ObjectContaining.prototype.asymmetricMatch = function(other) { + if (typeof(this.sample) !== 'object') { throw new Error('You must provide an object to objectContaining, not \''+this.sample+'\'.'); } + + for (var property in this.sample) { + if (!hasProperty(other, property) || + !j$.matchersUtil.equals(this.sample[property], other[property])) { + return false; + } + } + + return true; + }; + + ObjectContaining.prototype.jasmineToString = function() { + return ''; + }; + + return ObjectContaining; +}; + +getJasmineRequireObj().StringMatching = function(j$) { + + function StringMatching(expected) { + if (!j$.isString_(expected) && !j$.isA_('RegExp', expected)) { + throw new Error('Expected is not a String or a RegExp'); + } + + this.regexp = new RegExp(expected); + } + + StringMatching.prototype.asymmetricMatch = function(other) { + return this.regexp.test(other); + }; + + StringMatching.prototype.jasmineToString = function() { + return ''; + }; + + return StringMatching; +}; + +getJasmineRequireObj().errors = function() { + function ExpectationFailed() {} + + ExpectationFailed.prototype = new Error(); + ExpectationFailed.prototype.constructor = ExpectationFailed; + + return { + ExpectationFailed: ExpectationFailed + }; +}; getJasmineRequireObj().matchersUtil = function(j$) { // TODO: what to do about jasmine.pp not being inject? move to JSON.stringify? gut PrettyPrinter? @@ -1748,7 +2682,9 @@ getJasmineRequireObj().matchersUtil = function(j$) { contains: function(haystack, needle, customTesters) { customTesters = customTesters || []; - if (Object.prototype.toString.apply(haystack) === "[object Array]") { + if ((Object.prototype.toString.apply(haystack) === '[object Array]') || + (!!haystack && !haystack.indexOf)) + { for (var i = 0; i < haystack.length; i++) { if (eq(haystack[i], needle, [], [], customTesters)) { return true; @@ -1756,7 +2692,8 @@ getJasmineRequireObj().matchersUtil = function(j$) { } return false; } - return haystack.indexOf(needle) >= 0; + + return !!haystack && haystack.indexOf(needle) >= 0; }, buildFailureMessage: function() { @@ -1767,29 +2704,55 @@ getJasmineRequireObj().matchersUtil = function(j$) { expected = args.slice(3), englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); }); - var message = "Expected " + + var message = 'Expected ' + j$.pp(actual) + - (isNot ? " not " : " ") + + (isNot ? ' not ' : ' ') + englishyPredicate; if (expected.length > 0) { for (var i = 0; i < expected.length; i++) { if (i > 0) { - message += ","; + message += ','; } - message += " " + j$.pp(expected[i]); + message += ' ' + j$.pp(expected[i]); } } - return message + "."; + return message + '.'; } }; + function isAsymmetric(obj) { + return obj && j$.isA_('Function', obj.asymmetricMatch); + } + + function asymmetricMatch(a, b) { + var asymmetricA = isAsymmetric(a), + asymmetricB = isAsymmetric(b); + + if (asymmetricA && asymmetricB) { + return undefined; + } + + if (asymmetricA) { + return a.asymmetricMatch(b); + } + + if (asymmetricB) { + return b.asymmetricMatch(a); + } + } + // Equality function lovingly adapted from isEqual in // [Underscore](http://underscorejs.org) function eq(a, b, aStack, bStack, customTesters) { var result = true; + var asymmetricResult = asymmetricMatch(a, b); + if (!j$.util.isUndefined(asymmetricResult)) { + return asymmetricResult; + } + for (var i = 0; i < customTesters.length; i++) { var customTesterResult = customTesters[i](a, b); if (!j$.util.isUndefined(customTesterResult)) { @@ -1797,27 +2760,6 @@ getJasmineRequireObj().matchersUtil = function(j$) { } } - if (a instanceof j$.Any) { - result = a.jasmineMatches(b); - if (result) { - return true; - } - } - - if (b instanceof j$.Any) { - result = b.jasmineMatches(a); - if (result) { - return true; - } - } - - if (b instanceof j$.ObjectContaining) { - result = b.jasmineMatches(a); - if (result) { - return true; - } - } - if (a instanceof Error && b instanceof Error) { return a.message == b.message; } @@ -1853,6 +2795,29 @@ getJasmineRequireObj().matchersUtil = function(j$) { a.ignoreCase == b.ignoreCase; } if (typeof a != 'object' || typeof b != 'object') { return false; } + + var aIsDomNode = j$.isDomNode(a); + var bIsDomNode = j$.isDomNode(b); + if (aIsDomNode && bIsDomNode) { + // At first try to use DOM3 method isEqualNode + if (a.isEqualNode) { + return a.isEqualNode(b); + } + // IE8 doesn't support isEqualNode, try to use outerHTML && innerText + var aIsElement = a instanceof Element; + var bIsElement = b instanceof Element; + if (aIsElement && bIsElement) { + return a.outerHTML == b.outerHTML; + } + if (aIsElement || bIsElement) { + return false; + } + return a.innerText == b.innerText && a.textContent == b.textContent; + } + if (aIsDomNode || bIsDomNode) { + return false; + } + // Assume equality for cyclic structures. The algorithm for detecting cyclic // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. var length = aStack.length; @@ -1866,23 +2831,20 @@ getJasmineRequireObj().matchersUtil = function(j$) { bStack.push(b); var size = 0; // Recursively compare objects and arrays. - if (className == '[object Array]') { - // Compare array lengths to determine if a deep comparison is necessary. - size = a.length; - result = size == b.length; - if (result) { - // Deep compare the contents, ignoring non-numeric properties. - while (size--) { - if (!(result = eq(a[size], b[size], aStack, bStack, customTesters))) { break; } - } - } - } else { + // Compare array lengths to determine if a deep comparison is necessary. + if (className == '[object Array]' && a.length !== b.length) { + result = false; + } + + if (result) { // Objects with different constructors are not equivalent, but `Object`s - // from different frames are. - var aCtor = a.constructor, bCtor = b.constructor; - if (aCtor !== bCtor && !(isFunction(aCtor) && (aCtor instanceof aCtor) && - isFunction(bCtor) && (bCtor instanceof bCtor))) { - return false; + // or `Array`s from different frames are. + if (className !== '[object Array]') { + var aCtor = a.constructor, bCtor = b.constructor; + if (aCtor !== bCtor && !(isFunction(aCtor) && aCtor instanceof aCtor && + isFunction(bCtor) && bCtor instanceof bCtor)) { + return false; + } } // Deep compare objects. for (var key in a) { @@ -1908,7 +2870,7 @@ getJasmineRequireObj().matchersUtil = function(j$) { return result; function has(obj, key) { - return obj.hasOwnProperty(key); + return Object.prototype.hasOwnProperty.call(obj, key); } function isFunction(obj) { @@ -2018,9 +2980,9 @@ getJasmineRequireObj().toBeNaN = function(j$) { }; if (result.pass) { - result.message = "Expected actual not to be NaN."; + result.message = 'Expected actual not to be NaN.'; } else { - result.message = "Expected " + j$.pp(actual) + " to be NaN."; + result.message = function() { return 'Expected ' + j$.pp(actual) + ' to be NaN.'; }; } return result; @@ -2132,8 +3094,8 @@ getJasmineRequireObj().toHaveBeenCalled = function(j$) { result.pass = actual.calls.any(); result.message = result.pass ? - "Expected spy " + actual.and.identity() + " not to have been called." : - "Expected spy " + actual.and.identity() + " to have been called."; + 'Expected spy ' + actual.and.identity() + ' not to have been called.' : + 'Expected spy ' + actual.and.identity() + ' to have been called.'; return result; } @@ -2143,9 +3105,40 @@ getJasmineRequireObj().toHaveBeenCalled = function(j$) { return toHaveBeenCalled; }; +getJasmineRequireObj().toHaveBeenCalledTimes = function(j$) { + + function toHaveBeenCalledTimes() { + return { + compare: function(actual, expected) { + if (!j$.isSpy(actual)) { + throw new Error('Expected a spy, but got ' + j$.pp(actual) + '.'); + } + + var args = Array.prototype.slice.call(arguments, 0), + result = { pass: false }; + + if(!expected){ + throw new Error('Expected times failed is required as an argument.'); + } + + actual = args[0]; + var calls = actual.calls.count(); + var timesMessage = expected === 1 ? 'once' : expected + ' times'; + result.pass = calls === expected; + result.message = result.pass ? + 'Expected spy ' + actual.and.identity() + ' not to have been called ' + timesMessage + '. It was called ' + calls + ' times.' : + 'Expected spy ' + actual.and.identity() + ' to have been called ' + timesMessage + '. It was called ' + calls + ' times.'; + return result; + } + }; + } + + return toHaveBeenCalledTimes; +}; + getJasmineRequireObj().toHaveBeenCalledWith = function(j$) { - function toHaveBeenCalledWith(util) { + function toHaveBeenCalledWith(util, customEqualityTesters) { return { compare: function() { var args = Array.prototype.slice.call(arguments, 0), @@ -2158,15 +3151,15 @@ getJasmineRequireObj().toHaveBeenCalledWith = function(j$) { } if (!actual.calls.any()) { - result.message = "Expected spy " + actual.and.identity() + " to have been called with " + j$.pp(expectedArgs) + " but it was never called."; + result.message = function() { return 'Expected spy ' + actual.and.identity() + ' to have been called with ' + j$.pp(expectedArgs) + ' but it was never called.'; }; return result; } - if (util.contains(actual.calls.allArgs(), expectedArgs)) { + if (util.contains(actual.calls.allArgs(), expectedArgs, customEqualityTesters)) { result.pass = true; - result.message = "Expected spy " + actual.and.identity() + " not to have been called with " + j$.pp(expectedArgs) + " but it was."; + result.message = function() { return 'Expected spy ' + actual.and.identity() + ' not to have been called with ' + j$.pp(expectedArgs) + ' but it was.'; }; } else { - result.message = "Expected spy " + actual.and.identity() + " to have been called with " + j$.pp(expectedArgs) + " but actual calls were " + j$.pp(actual.calls.allArgs()).replace(/^\[ | \]$/g, '') + "."; + result.message = function() { return 'Expected spy ' + actual.and.identity() + ' to have been called with ' + j$.pp(expectedArgs) + ' but actual calls were ' + j$.pp(actual.calls.allArgs()).replace(/^\[ | \]$/g, '') + '.'; }; } return result; @@ -2177,11 +3170,15 @@ getJasmineRequireObj().toHaveBeenCalledWith = function(j$) { return toHaveBeenCalledWith; }; -getJasmineRequireObj().toMatch = function() { +getJasmineRequireObj().toMatch = function(j$) { function toMatch() { return { compare: function(actual, expected) { + if (!j$.isString_(expected) && !j$.isA_('RegExp', expected)) { + throw new Error('Expected is not a String or a RegExp'); + } + var regexp = new RegExp(expected); return { @@ -2203,8 +3200,8 @@ getJasmineRequireObj().toThrow = function(j$) { threw = false, thrown; - if (typeof actual != "function") { - throw new Error("Actual is not a Function"); + if (typeof actual != 'function') { + throw new Error('Actual is not a Function'); } try { @@ -2215,22 +3212,22 @@ getJasmineRequireObj().toThrow = function(j$) { } if (!threw) { - result.message = "Expected function to throw an exception."; + result.message = 'Expected function to throw an exception.'; return result; } if (arguments.length == 1) { result.pass = true; - result.message = "Expected function not to throw, but it threw " + j$.pp(thrown) + "."; + result.message = function() { return 'Expected function not to throw, but it threw ' + j$.pp(thrown) + '.'; }; return result; } if (util.equals(thrown, expected)) { result.pass = true; - result.message = "Expected function not to throw " + j$.pp(expected) + "."; + result.message = function() { return 'Expected function not to throw ' + j$.pp(expected) + '.'; }; } else { - result.message = "Expected function to throw " + j$.pp(expected) + ", but it threw " + j$.pp(thrown) + "."; + result.message = function() { return 'Expected function to throw ' + j$.pp(expected) + ', but it threw ' + j$.pp(thrown) + '.'; }; } return result; @@ -2242,22 +3239,19 @@ getJasmineRequireObj().toThrow = function(j$) { }; getJasmineRequireObj().toThrowError = function(j$) { - function toThrowError (util) { + function toThrowError () { return { compare: function(actual) { var threw = false, - thrown, - errorType, - message, - regexp, - name, - constructorName; + pass = {pass: true}, + fail = {pass: false}, + thrown; - if (typeof actual != "function") { - throw new Error("Actual is not a Function"); + if (typeof actual != 'function') { + throw new Error('Actual is not a Function'); } - extractExpectedParams.apply(null, arguments); + var errorMatcher = getMatcher.apply(null, arguments); try { actual(); @@ -2267,136 +3261,198 @@ getJasmineRequireObj().toThrowError = function(j$) { } if (!threw) { - return fail("Expected function to throw an Error."); + fail.message = 'Expected function to throw an Error.'; + return fail; } if (!(thrown instanceof Error)) { - return fail("Expected function to throw an Error, but it threw " + thrown + "."); + fail.message = function() { return 'Expected function to throw an Error, but it threw ' + j$.pp(thrown) + '.'; }; + return fail; } - if (arguments.length == 1) { - return pass("Expected function not to throw an Error, but it threw " + fnNameFor(thrown) + "."); + if (errorMatcher.hasNoSpecifics()) { + pass.message = 'Expected function not to throw an Error, but it threw ' + j$.fnNameFor(thrown) + '.'; + return pass; } - if (errorType) { - name = fnNameFor(errorType); - constructorName = fnNameFor(thrown.constructor); - } - - if (errorType && message) { - if (thrown.constructor == errorType && util.equals(thrown.message, message)) { - return pass("Expected function not to throw " + name + " with message \"" + message + "\"."); - } else { - return fail("Expected function to throw " + name + " with message \"" + message + - "\", but it threw " + constructorName + " with message \"" + thrown.message + "\"."); - } - } - - if (errorType && regexp) { - if (thrown.constructor == errorType && regexp.test(thrown.message)) { - return pass("Expected function not to throw " + name + " with message matching " + regexp + "."); - } else { - return fail("Expected function to throw " + name + " with message matching " + regexp + - ", but it threw " + constructorName + " with message \"" + thrown.message + "\"."); - } - } - - if (errorType) { - if (thrown.constructor == errorType) { - return pass("Expected function not to throw " + name + "."); - } else { - return fail("Expected function to throw " + name + ", but it threw " + constructorName + "."); - } - } - - if (message) { - if (thrown.message == message) { - return pass("Expected function not to throw an exception with message " + j$.pp(message) + "."); - } else { - return fail("Expected function to throw an exception with message " + j$.pp(message) + - ", but it threw an exception with message " + j$.pp(thrown.message) + "."); - } - } - - if (regexp) { - if (regexp.test(thrown.message)) { - return pass("Expected function not to throw an exception with a message matching " + j$.pp(regexp) + "."); - } else { - return fail("Expected function to throw an exception with a message matching " + j$.pp(regexp) + - ", but it threw an exception with message " + j$.pp(thrown.message) + "."); - } - } - - function fnNameFor(func) { - return func.name || func.toString().match(/^\s*function\s*(\w*)\s*\(/)[1]; - } - - function pass(notMessage) { - return { - pass: true, - message: notMessage + if (errorMatcher.matches(thrown)) { + pass.message = function() { + return 'Expected function not to throw ' + errorMatcher.errorTypeDescription + errorMatcher.messageDescription() + '.'; }; - } - - function fail(message) { - return { - pass: false, - message: message + return pass; + } else { + fail.message = function() { + return 'Expected function to throw ' + errorMatcher.errorTypeDescription + errorMatcher.messageDescription() + + ', but it threw ' + errorMatcher.thrownDescription(thrown) + '.'; }; - } - - function extractExpectedParams() { - if (arguments.length == 1) { - return; - } - - if (arguments.length == 2) { - var expected = arguments[1]; - - if (expected instanceof RegExp) { - regexp = expected; - } else if (typeof expected == "string") { - message = expected; - } else if (checkForAnErrorType(expected)) { - errorType = expected; - } - - if (!(errorType || message || regexp)) { - throw new Error("Expected is not an Error, string, or RegExp."); - } - } else { - if (checkForAnErrorType(arguments[1])) { - errorType = arguments[1]; - } else { - throw new Error("Expected error type is not an Error."); - } - - if (arguments[2] instanceof RegExp) { - regexp = arguments[2]; - } else if (typeof arguments[2] == "string") { - message = arguments[2]; - } else { - throw new Error("Expected error message is not a string or RegExp."); - } - } - } - - function checkForAnErrorType(type) { - if (typeof type !== "function") { - return false; - } - - var Surrogate = function() {}; - Surrogate.prototype = type.prototype; - return (new Surrogate()) instanceof Error; + return fail; } } }; + + function getMatcher() { + var expected = null, + errorType = null; + + if (arguments.length == 2) { + expected = arguments[1]; + if (isAnErrorType(expected)) { + errorType = expected; + expected = null; + } + } else if (arguments.length > 2) { + errorType = arguments[1]; + expected = arguments[2]; + if (!isAnErrorType(errorType)) { + throw new Error('Expected error type is not an Error.'); + } + } + + if (expected && !isStringOrRegExp(expected)) { + if (errorType) { + throw new Error('Expected error message is not a string or RegExp.'); + } else { + throw new Error('Expected is not an Error, string, or RegExp.'); + } + } + + function messageMatch(message) { + if (typeof expected == 'string') { + return expected == message; + } else { + return expected.test(message); + } + } + + return { + errorTypeDescription: errorType ? j$.fnNameFor(errorType) : 'an exception', + thrownDescription: function(thrown) { + var thrownName = errorType ? j$.fnNameFor(thrown.constructor) : 'an exception', + thrownMessage = ''; + + if (expected) { + thrownMessage = ' with message ' + j$.pp(thrown.message); + } + + return thrownName + thrownMessage; + }, + messageDescription: function() { + if (expected === null) { + return ''; + } else if (expected instanceof RegExp) { + return ' with a message matching ' + j$.pp(expected); + } else { + return ' with message ' + j$.pp(expected); + } + }, + hasNoSpecifics: function() { + return expected === null && errorType === null; + }, + matches: function(error) { + return (errorType === null || error instanceof errorType) && + (expected === null || messageMatch(error.message)); + } + }; + } + + function isStringOrRegExp(potential) { + return potential instanceof RegExp || (typeof potential == 'string'); + } + + function isAnErrorType(type) { + if (typeof type !== 'function') { + return false; + } + + var Surrogate = function() {}; + Surrogate.prototype = type.prototype; + return (new Surrogate()) instanceof Error; + } } return toThrowError; }; -getJasmineRequireObj().version = function() { - return "2.0.0"; +getJasmineRequireObj().interface = function(jasmine, env) { + var jasmineInterface = { + describe: function(description, specDefinitions) { + return env.describe(description, specDefinitions); + }, + + xdescribe: function(description, specDefinitions) { + return env.xdescribe(description, specDefinitions); + }, + + fdescribe: function(description, specDefinitions) { + return env.fdescribe(description, specDefinitions); + }, + + it: function() { + return env.it.apply(env, arguments); + }, + + xit: function() { + return env.xit.apply(env, arguments); + }, + + fit: function() { + return env.fit.apply(env, arguments); + }, + + beforeEach: function() { + return env.beforeEach.apply(env, arguments); + }, + + afterEach: function() { + return env.afterEach.apply(env, arguments); + }, + + beforeAll: function() { + return env.beforeAll.apply(env, arguments); + }, + + afterAll: function() { + return env.afterAll.apply(env, arguments); + }, + + expect: function(actual) { + return env.expect(actual); + }, + + pending: function() { + return env.pending.apply(env, arguments); + }, + + fail: function() { + return env.fail.apply(env, arguments); + }, + + spyOn: function(obj, methodName) { + return env.spyOn(obj, methodName); + }, + + jsApiReporter: new jasmine.JsApiReporter({ + timer: new jasmine.Timer() + }), + + jasmine: jasmine + }; + + jasmine.addCustomEqualityTester = function(tester) { + env.addCustomEqualityTester(tester); + }; + + jasmine.addMatchers = function(matchers) { + return env.addMatchers(matchers); + }; + + jasmine.clock = function() { + return env.clock; + }; + + return jasmineInterface; }; + +getJasmineRequireObj().version = function() { + return '2.4.1'; +}; \ No newline at end of file