vendor/assets/javascripts/sinon.js in sinon-rails-1.7.3 vs vendor/assets/javascripts/sinon.js in sinon-rails-1.9.0

- old
+ new

@@ -1,14 +1,14 @@ /** - * Sinon.JS 1.7.3, 2013/06/20 + * Sinon.JS 1.9.0, 2014/03/05 * * @author Christian Johansen (christian@cjohansen.no) * @author Contributors: https://github.com/cjohansen/Sinon.JS/blob/master/AUTHORS * * (The BSD License) * - * Copyright (c) 2010-2013, Christian Johansen, christian@cjohansen.no + * Copyright (c) 2010-2014, Christian Johansen, christian@cjohansen.no * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * @@ -32,429 +32,589 @@ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ this.sinon = (function () { -var buster = (function (setTimeout, B) { - var isNode = typeof require == "function" && typeof module == "object"; - var div = typeof document != "undefined" && document.createElement("div"); - var F = function () {}; +var samsam, formatio; +function define(mod, deps, fn) { if (mod == "samsam") { samsam = deps(); } else { formatio = fn(samsam); } } +define.amd = true; +((typeof define === "function" && define.amd && function (m) { define("samsam", m); }) || + (typeof module === "object" && + function (m) { module.exports = m(); }) || // Node + function (m) { this.samsam = m(); } // Browser globals +)(function () { + var o = Object.prototype; + var div = typeof document !== "undefined" && document.createElement("div"); - var buster = { - bind: function bind(obj, methOrProp) { - var method = typeof methOrProp == "string" ? obj[methOrProp] : methOrProp; - var args = Array.prototype.slice.call(arguments, 2); - return function () { - var allArgs = args.concat(Array.prototype.slice.call(arguments)); - return method.apply(obj, allArgs); - }; - }, + function isNaN(value) { + // Unlike global isNaN, this avoids type coercion + // typeof check avoids IE host object issues, hat tip to + // lodash + var val = value; // JsLint thinks value !== value is "weird" + return typeof value === "number" && value !== val; + } - partial: function partial(fn) { - var args = [].slice.call(arguments, 1); - return function () { - return fn.apply(this, args.concat([].slice.call(arguments))); - }; - }, + function getClass(value) { + // Returns the internal [[Class]] by calling Object.prototype.toString + // with the provided value as this. Return value is a string, naming the + // internal class, e.g. "Array" + return o.toString.call(value).split(/[ \]]/)[1]; + } - create: function create(object) { - F.prototype = object; - return new F(); - }, + /** + * @name samsam.isArguments + * @param Object object + * + * Returns ``true`` if ``object`` is an ``arguments`` object, + * ``false`` otherwise. + */ + function isArguments(object) { + if (typeof object !== "object" || typeof object.length !== "number" || + getClass(object) === "Array") { + return false; + } + if (typeof object.callee == "function") { return true; } + try { + object[object.length] = 6; + delete object[object.length]; + } catch (e) { + return true; + } + return false; + } - extend: function extend(target) { - if (!target) { return; } - for (var i = 1, l = arguments.length, prop; i < l; ++i) { - for (prop in arguments[i]) { - target[prop] = arguments[i][prop]; - } - } - return target; - }, + /** + * @name samsam.isElement + * @param Object object + * + * Returns ``true`` if ``object`` is a DOM element node. Unlike + * Underscore.js/lodash, this function will return ``false`` if ``object`` + * is an *element-like* object, i.e. a regular object with a ``nodeType`` + * property that holds the value ``1``. + */ + function isElement(object) { + if (!object || object.nodeType !== 1 || !div) { return false; } + try { + object.appendChild(div); + object.removeChild(div); + } catch (e) { + return false; + } + return true; + } - nextTick: function nextTick(callback) { - if (typeof process != "undefined" && process.nextTick) { - return process.nextTick(callback); - } - setTimeout(callback, 0); - }, + /** + * @name samsam.keys + * @param Object object + * + * Return an array of own property names. + */ + function keys(object) { + var ks = [], prop; + for (prop in object) { + if (o.hasOwnProperty.call(object, prop)) { ks.push(prop); } + } + return ks; + } - functionName: function functionName(func) { - if (!func) return ""; - if (func.displayName) return func.displayName; - if (func.name) return func.name; - var matches = func.toString().match(/function\s+([^\(]+)/m); - return matches && matches[1] || ""; - }, + /** + * @name samsam.isDate + * @param Object value + * + * Returns true if the object is a ``Date``, or *date-like*. Duck typing + * of date objects work by checking that the object has a ``getTime`` + * function whose return value equals the return value from the object's + * ``valueOf``. + */ + function isDate(value) { + return typeof value.getTime == "function" && + value.getTime() == value.valueOf(); + } - isNode: function isNode(obj) { - if (!div) return false; - try { - obj.appendChild(div); - obj.removeChild(div); - } catch (e) { - return false; - } - return true; - }, + /** + * @name samsam.isNegZero + * @param Object value + * + * Returns ``true`` if ``value`` is ``-0``. + */ + function isNegZero(value) { + return value === 0 && 1 / value === -Infinity; + } - isElement: function isElement(obj) { - return obj && obj.nodeType === 1 && buster.isNode(obj); - }, + /** + * @name samsam.equal + * @param Object obj1 + * @param Object obj2 + * + * Returns ``true`` if two objects are strictly equal. Compared to + * ``===`` there are two exceptions: + * + * - NaN is considered equal to NaN + * - -0 and +0 are not considered equal + */ + function identical(obj1, obj2) { + if (obj1 === obj2 || (isNaN(obj1) && isNaN(obj2))) { + return obj1 !== 0 || isNegZero(obj1) === isNegZero(obj2); + } + } - isArray: function isArray(arr) { - return Object.prototype.toString.call(arr) == "[object Array]"; - }, - flatten: function flatten(arr) { - var result = [], arr = arr || []; - for (var i = 0, l = arr.length; i < l; ++i) { - result = result.concat(buster.isArray(arr[i]) ? flatten(arr[i]) : arr[i]); + /** + * @name samsam.deepEqual + * @param Object obj1 + * @param Object obj2 + * + * Deep equal comparison. Two values are "deep equal" if: + * + * - They are equal, according to samsam.identical + * - They are both date objects representing the same time + * - They are both arrays containing elements that are all deepEqual + * - They are objects with the same set of properties, and each property + * in ``obj1`` is deepEqual to the corresponding property in ``obj2`` + * + * Supports cyclic objects. + */ + function deepEqualCyclic(obj1, obj2) { + + // used for cyclic comparison + // contain already visited objects + var objects1 = [], + objects2 = [], + // contain pathes (position in the object structure) + // of the already visited objects + // indexes same as in objects arrays + paths1 = [], + paths2 = [], + // contains combinations of already compared objects + // in the manner: { "$1['ref']$2['ref']": true } + compared = {}; + + /** + * used to check, if the value of a property is an object + * (cyclic logic is only needed for objects) + * only needed for cyclic logic + */ + function isObject(value) { + + if (typeof value === 'object' && value !== null && + !(value instanceof Boolean) && + !(value instanceof Date) && + !(value instanceof Number) && + !(value instanceof RegExp) && + !(value instanceof String)) { + + return true; } - return result; - }, - each: function each(arr, callback) { - for (var i = 0, l = arr.length; i < l; ++i) { - callback(arr[i]); + return false; + } + + /** + * returns the index of the given object in the + * given objects array, -1 if not contained + * only needed for cyclic logic + */ + function getIndex(objects, obj) { + + var i; + for (i = 0; i < objects.length; i++) { + if (objects[i] === obj) { + return i; + } } - }, - map: function map(arr, callback) { - var results = []; - for (var i = 0, l = arr.length; i < l; ++i) { - results.push(callback(arr[i])); + return -1; + } + + // does the recursion for the deep equal check + return (function deepEqual(obj1, obj2, path1, path2) { + var type1 = typeof obj1; + var type2 = typeof obj2; + + // == null also matches undefined + if (obj1 === obj2 || + isNaN(obj1) || isNaN(obj2) || + obj1 == null || obj2 == null || + type1 !== "object" || type2 !== "object") { + + return identical(obj1, obj2); } - return results; - }, - parallel: function parallel(fns, callback) { - function cb(err, res) { - if (typeof callback == "function") { - callback(err, res); - callback = null; + // Elements are only equal if identical(expected, actual) + if (isElement(obj1) || isElement(obj2)) { return false; } + + var isDate1 = isDate(obj1), isDate2 = isDate(obj2); + if (isDate1 || isDate2) { + if (!isDate1 || !isDate2 || obj1.getTime() !== obj2.getTime()) { + return false; } } - if (fns.length == 0) { return cb(null, []); } - var remaining = fns.length, results = []; - function makeDone(num) { - return function done(err, result) { - if (err) { return cb(err); } - results[num] = result; - if (--remaining == 0) { cb(null, results); } - }; + + if (obj1 instanceof RegExp && obj2 instanceof RegExp) { + if (obj1.toString() !== obj2.toString()) { return false; } } - for (var i = 0, l = fns.length; i < l; ++i) { - fns[i](makeDone(i)); - } - }, - series: function series(fns, callback) { - function cb(err, res) { - if (typeof callback == "function") { - callback(err, res); + var class1 = getClass(obj1); + var class2 = getClass(obj2); + var keys1 = keys(obj1); + var keys2 = keys(obj2); + + if (isArguments(obj1) || isArguments(obj2)) { + if (obj1.length !== obj2.length) { return false; } + } else { + if (type1 !== type2 || class1 !== class2 || + keys1.length !== keys2.length) { + return false; } } - var remaining = fns.slice(); - var results = []; - function callNext() { - if (remaining.length == 0) return cb(null, results); - var promise = remaining.shift()(next); - if (promise && typeof promise.then == "function") { - promise.then(buster.partial(next, null), next); + + var key, i, l, + // following vars are used for the cyclic logic + value1, value2, + isObject1, isObject2, + index1, index2, + newPath1, newPath2; + + for (i = 0, l = keys1.length; i < l; i++) { + key = keys1[i]; + if (!o.hasOwnProperty.call(obj2, key)) { + return false; } - } - function next(err, result) { - if (err) return cb(err); - results.push(result); - callNext(); - } - callNext(); - }, - countdown: function countdown(num, done) { - return function () { - if (--num == 0) done(); - }; - } - }; + // Start of the cyclic logic - if (typeof process === "object" && - typeof require === "function" && typeof module === "object") { - var crypto = require("crypto"); - var path = require("path"); + value1 = obj1[key]; + value2 = obj2[key]; - buster.tmpFile = function (fileName) { - var hashed = crypto.createHash("sha1"); - hashed.update(fileName); - var tmpfileName = hashed.digest("hex"); + isObject1 = isObject(value1); + isObject2 = isObject(value2); - if (process.platform == "win32") { - return path.join(process.env["TEMP"], tmpfileName); - } else { - return path.join("/tmp", tmpfileName); - } - }; - } + // determine, if the objects were already visited + // (it's faster to check for isObject first, than to + // get -1 from getIndex for non objects) + index1 = isObject1 ? getIndex(objects1, value1) : -1; + index2 = isObject2 ? getIndex(objects2, value2) : -1; - if (Array.prototype.some) { - buster.some = function (arr, fn, thisp) { - return arr.some(fn, thisp); - }; - } else { - // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/some - buster.some = function (arr, fun, thisp) { - if (arr == null) { throw new TypeError(); } - arr = Object(arr); - var len = arr.length >>> 0; - if (typeof fun !== "function") { throw new TypeError(); } + // determine the new pathes of the objects + // - for non cyclic objects the current path will be extended + // by current property name + // - for cyclic objects the stored path is taken + newPath1 = index1 !== -1 + ? paths1[index1] + : path1 + '[' + JSON.stringify(key) + ']'; + newPath2 = index2 !== -1 + ? paths2[index2] + : path2 + '[' + JSON.stringify(key) + ']'; - for (var i = 0; i < len; i++) { - if (arr.hasOwnProperty(i) && fun.call(thisp, arr[i], i, arr)) { + // stop recursion if current objects are already compared + if (compared[newPath1 + newPath2]) { return true; } - } - return false; - }; - } + // remember the current objects and their pathes + if (index1 === -1 && isObject1) { + objects1.push(value1); + paths1.push(newPath1); + } + if (index2 === -1 && isObject2) { + objects2.push(value2); + paths2.push(newPath2); + } - if (Array.prototype.filter) { - buster.filter = function (arr, fn, thisp) { - return arr.filter(fn, thisp); - }; - } else { - // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/filter - buster.filter = function (fn, thisp) { - if (this == null) { throw new TypeError(); } + // remember that the current objects are already compared + if (isObject1 && isObject2) { + compared[newPath1 + newPath2] = true; + } - var t = Object(this); - var len = t.length >>> 0; - if (typeof fn != "function") { throw new TypeError(); } + // End of cyclic logic - var res = []; - for (var i = 0; i < len; i++) { - if (i in t) { - var val = t[i]; // in case fun mutates this - if (fn.call(thisp, val, i, t)) { res.push(val); } + // neither value1 nor value2 is a cycle + // continue with next level + if (!deepEqual(value1, value2, newPath1, newPath2)) { + return false; } } - return res; - }; + return true; + + }(obj1, obj2, '$1', '$2')); } - if (isNode) { - module.exports = buster; - buster.eventEmitter = require("./buster-event-emitter"); - Object.defineProperty(buster, "defineVersionGetter", { - get: function () { - return require("./define-version-getter"); + var match; + + function arrayContains(array, subset) { + if (subset.length === 0) { return true; } + var i, l, j, k; + for (i = 0, l = array.length; i < l; ++i) { + if (match(array[i], subset[0])) { + for (j = 0, k = subset.length; j < k; ++j) { + if (!match(array[i + j], subset[j])) { return false; } + } + return true; } - }); + } + return false; } - return buster.extend(B || {}, buster); -}(setTimeout, buster)); -if (typeof buster === "undefined") { - var buster = {}; -} + /** + * @name samsam.match + * @param Object object + * @param Object matcher + * + * Compare arbitrary value ``object`` with matcher. + */ + match = function match(object, matcher) { + if (matcher && typeof matcher.test === "function") { + return matcher.test(object); + } -if (typeof module === "object" && typeof require === "function") { - buster = require("buster-core"); -} + if (typeof matcher === "function") { + return matcher(object) === true; + } -buster.format = buster.format || {}; -buster.format.excludeConstructors = ["Object", /^.$/]; -buster.format.quoteStrings = true; + if (typeof matcher === "string") { + matcher = matcher.toLowerCase(); + var notNull = typeof object === "string" || !!object; + return notNull && + (String(object)).toLowerCase().indexOf(matcher) >= 0; + } -buster.format.ascii = (function () { + if (typeof matcher === "number") { + return matcher === object; + } + + if (typeof matcher === "boolean") { + return matcher === object; + } + + if (getClass(object) === "Array" && getClass(matcher) === "Array") { + return arrayContains(object, matcher); + } + + if (matcher && typeof matcher === "object") { + var prop; + for (prop in matcher) { + if (!match(object[prop], matcher[prop])) { + return false; + } + } + return true; + } + + throw new Error("Matcher was not a string, a number, a " + + "function, a boolean or an object"); + }; + + return { + isArguments: isArguments, + isElement: isElement, + isDate: isDate, + isNegZero: isNegZero, + identical: identical, + deepEqual: deepEqualCyclic, + match: match, + keys: keys + }; +}); +((typeof define === "function" && define.amd && function (m) { + define("formatio", ["samsam"], m); +}) || (typeof module === "object" && function (m) { + module.exports = m(require("samsam")); +}) || function (m) { this.formatio = m(this.samsam); } +)(function (samsam) { + var formatio = { + excludeConstructors: ["Object", /^.$/], + quoteStrings: true + }; + var hasOwn = Object.prototype.hasOwnProperty; var specialObjects = []; - if (typeof global != "undefined") { - specialObjects.push({ obj: global, value: "[object global]" }); + if (typeof global !== "undefined") { + specialObjects.push({ object: global, value: "[object global]" }); } - if (typeof document != "undefined") { - specialObjects.push({ obj: document, value: "[object HTMLDocument]" }); + if (typeof document !== "undefined") { + specialObjects.push({ + object: document, + value: "[object HTMLDocument]" + }); } - if (typeof window != "undefined") { - specialObjects.push({ obj: window, value: "[object Window]" }); + if (typeof window !== "undefined") { + specialObjects.push({ object: window, value: "[object Window]" }); } - function keys(object) { - var k = Object.keys && Object.keys(object) || []; + function functionName(func) { + if (!func) { return ""; } + if (func.displayName) { return func.displayName; } + if (func.name) { return func.name; } + var matches = func.toString().match(/function\s+([^\(]+)/m); + return (matches && matches[1]) || ""; + } - if (k.length == 0) { - for (var prop in object) { - if (hasOwn.call(object, prop)) { - k.push(prop); - } + function constructorName(f, object) { + var name = functionName(object && object.constructor); + var excludes = f.excludeConstructors || + formatio.excludeConstructors || []; + + var i, l; + for (i = 0, l = excludes.length; i < l; ++i) { + if (typeof excludes[i] === "string" && excludes[i] === name) { + return ""; + } else if (excludes[i].test && excludes[i].test(name)) { + return ""; } } - return k.sort(); + return name; } function isCircular(object, objects) { - if (typeof object != "object") { - return false; + if (typeof object !== "object") { return false; } + var i, l; + for (i = 0, l = objects.length; i < l; ++i) { + if (objects[i] === object) { return true; } } - - for (var i = 0, l = objects.length; i < l; ++i) { - if (objects[i] === object) { - return true; - } - } - return false; } - function ascii(object, processed, indent) { - if (typeof object == "string") { - var quote = typeof this.quoteStrings != "boolean" || this.quoteStrings; + function ascii(f, object, processed, indent) { + if (typeof object === "string") { + var qs = f.quoteStrings; + var quote = typeof qs !== "boolean" || qs; return processed || quote ? '"' + object + '"' : object; } - if (typeof object == "function" && !(object instanceof RegExp)) { + if (typeof object === "function" && !(object instanceof RegExp)) { return ascii.func(object); } processed = processed || []; - if (isCircular(object, processed)) { - return "[Circular]"; - } + if (isCircular(object, processed)) { return "[Circular]"; } - if (Object.prototype.toString.call(object) == "[object Array]") { - return ascii.array.call(this, object, processed); + if (Object.prototype.toString.call(object) === "[object Array]") { + return ascii.array.call(f, object, processed); } - if (!object) { - return "" + object; - } + if (!object) { return String((1/object) === -Infinity ? "-0" : object); } + if (samsam.isElement(object)) { return ascii.element(object); } - if (buster.isElement(object)) { - return ascii.element(object); - } - - if (typeof object.toString == "function" && - object.toString !== Object.prototype.toString) { + if (typeof object.toString === "function" && + object.toString !== Object.prototype.toString) { return object.toString(); } - for (var i = 0, l = specialObjects.length; i < l; i++) { - if (object === specialObjects[i].obj) { + var i, l; + for (i = 0, l = specialObjects.length; i < l; i++) { + if (object === specialObjects[i].object) { return specialObjects[i].value; } } - return ascii.object.call(this, object, processed, indent); + return ascii.object.call(f, object, processed, indent); } ascii.func = function (func) { - return "function " + buster.functionName(func) + "() {}"; + return "function " + functionName(func) + "() {}"; }; ascii.array = function (array, processed) { processed = processed || []; processed.push(array); - var pieces = []; - - for (var i = 0, l = array.length; i < l; ++i) { - pieces.push(ascii.call(this, array[i], processed)); + var i, l, pieces = []; + for (i = 0, l = array.length; i < l; ++i) { + pieces.push(ascii(this, array[i], processed)); } - return "[" + pieces.join(", ") + "]"; }; ascii.object = function (object, processed, indent) { processed = processed || []; processed.push(object); indent = indent || 0; - var pieces = [], properties = keys(object), prop, str, obj; - var is = ""; + var pieces = [], properties = samsam.keys(object).sort(); var length = 3; + var prop, str, obj, i, l; - for (var i = 0, l = indent; i < l; ++i) { - is += " "; - } - for (i = 0, l = properties.length; i < l; ++i) { prop = properties[i]; obj = object[prop]; if (isCircular(obj, processed)) { str = "[Circular]"; } else { - str = ascii.call(this, obj, processed, indent + 2); + str = ascii(this, obj, processed, indent + 2); } str = (/\s/.test(prop) ? '"' + prop + '"' : prop) + ": " + str; length += str.length; pieces.push(str); } - var cons = ascii.constructorName.call(this, object); - var prefix = cons ? "[" + cons + "] " : "" + var cons = constructorName(this, object); + var prefix = cons ? "[" + cons + "] " : ""; + var is = ""; + for (i = 0, l = indent; i < l; ++i) { is += " "; } - return (length + indent) > 80 ? - prefix + "{\n " + is + pieces.join(",\n " + is) + "\n" + is + "}" : - prefix + "{ " + pieces.join(", ") + " }"; + if (length + indent > 80) { + return prefix + "{\n " + is + pieces.join(",\n " + is) + "\n" + + is + "}"; + } + return prefix + "{ " + pieces.join(", ") + " }"; }; ascii.element = function (element) { var tagName = element.tagName.toLowerCase(); - var attrs = element.attributes, attribute, pairs = [], attrName; + var attrs = element.attributes, attr, pairs = [], attrName, i, l, val; - for (var i = 0, l = attrs.length; i < l; ++i) { - attribute = attrs.item(i); - attrName = attribute.nodeName.toLowerCase().replace("html:", ""); - - if (attrName == "contenteditable" && attribute.nodeValue == "inherit") { - continue; + for (i = 0, l = attrs.length; i < l; ++i) { + attr = attrs.item(i); + attrName = attr.nodeName.toLowerCase().replace("html:", ""); + val = attr.nodeValue; + if (attrName !== "contenteditable" || val !== "inherit") { + if (!!val) { pairs.push(attrName + "=\"" + val + "\""); } } - - if (!!attribute.nodeValue) { - pairs.push(attrName + "=\"" + attribute.nodeValue + "\""); - } } var formatted = "<" + tagName + (pairs.length > 0 ? " " : ""); var content = element.innerHTML; if (content.length > 20) { content = content.substr(0, 20) + "[...]"; } - var res = formatted + pairs.join(" ") + ">" + content + "</" + tagName + ">"; + var res = formatted + pairs.join(" ") + ">" + content + + "</" + tagName + ">"; return res.replace(/ contentEditable="inherit"/, ""); }; - ascii.constructorName = function (object) { - var name = buster.functionName(object && object.constructor); - var excludes = this.excludeConstructors || buster.format.excludeConstructors || []; - - for (var i = 0, l = excludes.length; i < l; ++i) { - if (typeof excludes[i] == "string" && excludes[i] == name) { - return ""; - } else if (excludes[i].test && excludes[i].test(name)) { - return ""; - } + function Formatio(options) { + for (var opt in options) { + this[opt] = options[opt]; } + } - return name; - }; + Formatio.prototype = { + functionName: functionName, - return ascii; -}()); + configure: function (options) { + return new Formatio(options); + }, -if (typeof module != "undefined") { - module.exports = buster.format; -} + constructorName: function (object) { + return constructorName(this, object); + }, + + ascii: function (object, processed, indent) { + return ascii(this, object, processed, indent); + } + }; + + return Formatio.prototype; +}); /*jslint eqeqeq: false, onevar: false, forin: true, nomen: false, regexp: false, plusplus: false*/ /*global module, require, __dirname, document*/ /** * Sinon core utilities. For internal use only. * @@ -462,11 +622,11 @@ * @license BSD * * Copyright (c) 2010-2013 Christian Johansen */ -var sinon = (function (buster) { +var sinon = (function (formatio) { var div = typeof document != "undefined" && document.createElement("div"); var hasOwn = Object.prototype.hasOwnProperty; function isDOMNode(obj) { var success = false; @@ -515,30 +675,42 @@ if (typeof method != "function") { throw new TypeError("Method wrapper should be function"); } - var wrappedMethod = object[property]; + var wrappedMethod = object[property], + error; if (!isFunction(wrappedMethod)) { - throw new TypeError("Attempted to wrap " + (typeof wrappedMethod) + " property " + + error = new TypeError("Attempted to wrap " + (typeof wrappedMethod) + " property " + property + " as function"); } if (wrappedMethod.restore && wrappedMethod.restore.sinon) { - throw new TypeError("Attempted to wrap " + property + " which is already wrapped"); + error = new TypeError("Attempted to wrap " + property + " which is already wrapped"); } if (wrappedMethod.calledBefore) { var verb = !!wrappedMethod.returns ? "stubbed" : "spied on"; - throw new TypeError("Attempted to wrap " + property + " which is already " + verb); + error = new TypeError("Attempted to wrap " + property + " which is already " + verb); } - // IE 8 does not support hasOwnProperty on the window object. - var owned = hasOwn.call(object, property); + if (error) { + if (wrappedMethod._stack) { + error.stack += '\n--------------\n' + wrappedMethod._stack; + } + throw error; + } + + // IE 8 does not support hasOwnProperty on the window object and Firefox has a problem + // when using hasOwn.call on objects from other frames. + var owned = object.hasOwnProperty ? object.hasOwnProperty(property) : hasOwn.call(object, property); object[property] = method; method.displayName = property; + // Set up a stack trace which can be used later to find what line of + // code the original method was created on. + method._stack = (new Error('Stack Trace for original')).stack; method.restore = function () { // For prototype properties try to reset by delete first. // If this fails (ex: localStorage on mobile safari) then force a reset // via direct assignment. @@ -598,35 +770,30 @@ if ((a === null && b !== null) || (a !== null && b === null)) { return false; } + if (a instanceof RegExp && b instanceof RegExp) { + return (a.source === b.source) && (a.global === b.global) && + (a.ignoreCase === b.ignoreCase) && (a.multiline === b.multiline); + } + var aString = Object.prototype.toString.call(a); if (aString != Object.prototype.toString.call(b)) { return false; } - if (aString == "[object Array]") { - if (a.length !== b.length) { - return false; - } - - for (var i = 0, l = a.length; i < l; i += 1) { - if (!deepEqual(a[i], b[i])) { - return false; - } - } - - return true; - } - if (aString == "[object Date]") { return a.valueOf() === b.valueOf(); } var prop, aLength = 0, bLength = 0; + if (aString == "[object Array]" && a.length !== b.length) { + return false; + } + for (prop in a) { aLength += 1; if (!deepEqual(a[prop], b[prop])) { return false; @@ -729,11 +896,11 @@ }, log: function () {}, logError: function (label, err) { - var msg = label + " threw exception: " + var msg = label + " threw exception: "; sinon.log(msg + "[" + err.name + "] " + err.message); if (err.stack) { sinon.log(err.stack); } setTimeout(function () { err.message = msg + err.message; @@ -771,18 +938,25 @@ object.restore(); } } }; - var isNode = typeof module == "object" && typeof require == "function"; + var isNode = typeof module !== "undefined" && module.exports; + var isAMD = typeof define === 'function' && typeof define.amd === 'object' && define.amd; - if (isNode) { + if (isAMD) { + define(function(){ + return sinon; + }); + } else if (isNode) { try { - buster = { format: require("buster-format") }; + formatio = require("formatio"); } catch (e) {} module.exports = sinon; module.exports.spy = require("./sinon/spy"); + module.exports.spyCall = require("./sinon/call"); + module.exports.behavior = require("./sinon/behavior"); module.exports.stub = require("./sinon/stub"); module.exports.mock = require("./sinon/mock"); module.exports.collection = require("./sinon/collection"); module.exports.assert = require("./sinon/assert"); module.exports.sandbox = require("./sinon/sandbox"); @@ -790,13 +964,12 @@ module.exports.testCase = require("./sinon/test_case"); module.exports.assert = require("./sinon/assert"); module.exports.match = require("./sinon/match"); } - if (buster) { - var formatter = sinon.create(buster.format); - formatter.quoteStrings = false; + if (formatio) { + var formatter = formatio.configure({ quoteStrings: false }); sinon.format = function () { return formatter.ascii.apply(formatter, arguments); }; } else if (isNode) { try { @@ -809,11 +982,11 @@ sorry */ } } return sinon; -}(typeof buster == "object" && buster)); +}(typeof formatio == "object" && formatio)); /* @depend ../sinon.js */ /*jslint eqeqeq: false, onevar: false, plusplus: false*/ /*global module, require, sinon*/ /** @@ -824,11 +997,11 @@ * * Copyright (c) 2012 Maximilian Antoni */ (function (sinon) { - var commonJSModule = typeof module == "object" && typeof require == "function"; + var commonJSModule = typeof module !== 'undefined' && module.exports; if (!sinon && commonJSModule) { sinon = require("../sinon"); } @@ -877,12 +1050,14 @@ } return true; } matcher.or = function (m2) { - if (!isMatcher(m2)) { + if (!arguments.length) { throw new TypeError("Matcher expected"); + } else if (!isMatcher(m2)) { + m2 = match(m2); } var m1 = this; var or = sinon.create(matcher); or.test = function (actual) { return m1.test(actual) || m2.test(actual); @@ -890,12 +1065,14 @@ or.message = m1.message + ".or(" + m2.message + ")"; return or; }; matcher.and = function (m2) { - if (!isMatcher(m2)) { + if (!arguments.length) { throw new TypeError("Matcher expected"); + } else if (!isMatcher(m2)) { + m2 = match(m2); } var m1 = this; var and = sinon.create(matcher); and.test = function (actual) { return m1.test(actual) && m2.test(actual); @@ -1067,17 +1244,20 @@ * * Copyright (c) 2010-2013 Christian Johansen * Copyright (c) 2013 Maximilian Antoni */ -var commonJSModule = typeof module == "object" && typeof require == "function"; +(function (sinon) { + var commonJSModule = typeof module !== 'undefined' && module.exports; + if (!sinon && commonJSModule) { + sinon = require("../sinon"); + } -if (!this.sinon && commonJSModule) { - var sinon = require("../sinon"); -} + if (!sinon) { + return; + } -(function (sinon) { function throwYieldError(proxy, text, args) { var msg = sinon.functionName(proxy) + text; if (args.length) { msg += " Received [" + slice.call(args).join(", ") + "]"; } @@ -1138,12 +1318,12 @@ } return this.exception === error || this.exception.name === error; }, - calledWithNew: function calledWithNew(thisValue) { - return this.thisValue instanceof this.proxy; + calledWithNew: function calledWithNew() { + return this.proxy.prototype && this.thisValue instanceof this.proxy; }, calledBefore: function (other) { return this.callId < other.callId; }, @@ -1239,18 +1419,24 @@ proxyCall.returnValue = returnValue; proxyCall.exception = exception; proxyCall.callId = id; return proxyCall; - }; + } createSpyCall.toString = callProto.toString; // used by mocks - sinon.spyCall = createSpyCall; + if (commonJSModule) { + module.exports = createSpyCall; + } else { + sinon.spyCall = createSpyCall; + } }(typeof sinon == "object" && sinon || null)); + /** * @depend ../sinon.js + * @depend call.js */ /*jslint eqeqeq: false, onevar: false, plusplus: false*/ /*global module, require, sinon*/ /** * Spy functions @@ -1260,15 +1446,23 @@ * * Copyright (c) 2010-2013 Christian Johansen */ (function (sinon) { - var commonJSModule = typeof module == "object" && typeof require == "function"; + var commonJSModule = typeof module !== 'undefined' && module.exports; var push = Array.prototype.push; var slice = Array.prototype.slice; var callId = 0; + if (!sinon && commonJSModule) { + sinon = require("../sinon"); + } + + if (!sinon) { + return; + } + function spy(object, property) { if (!property && typeof object == "function") { return spy.create(object); } @@ -1283,12 +1477,10 @@ function matchingFake(fakes, args, strict) { if (!fakes) { return; } - var alen = args.length; - for (var i = 0, l = fakes.length; i < l; i++) { if (fakes[i].matches(args, strict)) { return fakes[i]; } } @@ -1391,22 +1583,28 @@ if (matching) { returnValue = matching.invoke(func, thisValue, args); } else { returnValue = (this.func || func).apply(thisValue, args); } + + var thisCall = this.getCall(this.callCount - 1); + if (thisCall.calledWithNew() && typeof returnValue !== 'object') { + returnValue = thisValue; + } } catch (e) { - push.call(this.returnValues, undefined); exception = e; - throw e; - } finally { - push.call(this.exceptions, exception); } + push.call(this.exceptions, exception); push.call(this.returnValues, returnValue); createCallProperties.call(this); + if (exception !== undefined) { + throw exception; + } + return returnValue; }, getCall: function getCall(i) { if (i < 0 || i >= this.callCount) { @@ -1416,10 +1614,21 @@ return sinon.spyCall(this, this.thisValues[i], this.args[i], this.returnValues[i], this.exceptions[i], this.callIds[i]); }, + getCalls: function () { + var calls = []; + var i; + + for (i = 0; i < this.callCount; i++) { + calls.push(this.getCall(i)); + } + + return calls; + }, + calledBefore: function calledBefore(spyFn) { if (!this.called) { return false; } @@ -1452,10 +1661,11 @@ } var original = this; var fake = this._create(); fake.matchingAguments = args; + fake.parent = this; push.call(this.fakes, fake); fake.withArgs = function () { return original.withArgs.apply(original, arguments); }; @@ -1492,11 +1702,11 @@ return (format || "").replace(/%(.)/g, function (match, specifyer) { formatter = spyApi.formatters[specifyer]; if (typeof formatter == "function") { return formatter.call(null, spy, args); - } else if (!isNaN(parseInt(specifyer), 10)) { + } else if (!isNaN(parseInt(specifyer, 10))) { return sinon.format(args[specifyer - 1]); } return "%" + specifyer; }); @@ -1629,78 +1839,68 @@ } }(typeof sinon == "object" && sinon || null)); /** * @depend ../sinon.js - * @depend spy.js */ /*jslint eqeqeq: false, onevar: false*/ -/*global module, require, sinon*/ +/*global module, require, sinon, process, setImmediate, setTimeout*/ /** - * Stub functions + * Stub behavior * * @author Christian Johansen (christian@cjohansen.no) + * @author Tim Fischbach (mail@timfischbach.de) * @license BSD * * Copyright (c) 2010-2013 Christian Johansen */ (function (sinon) { - var commonJSModule = typeof module == "object" && typeof require == "function"; + var commonJSModule = typeof module !== 'undefined' && module.exports; if (!sinon && commonJSModule) { sinon = require("../sinon"); } if (!sinon) { return; } - function stub(object, property, func) { - if (!!func && typeof func != "function") { - throw new TypeError("Custom stub should be function"); - } + var slice = Array.prototype.slice; + var join = Array.prototype.join; + var proto; - var wrapper; - - if (func) { - wrapper = sinon.spy && sinon.spy.create ? sinon.spy.create(func) : func; + var nextTick = (function () { + if (typeof process === "object" && typeof process.nextTick === "function") { + return process.nextTick; + } else if (typeof setImmediate === "function") { + return setImmediate; } else { - wrapper = stub.create(); + return function (callback) { + setTimeout(callback, 0); + }; } + })(); - if (!object && !property) { - return sinon.stub.create(); + function throwsException(error, message) { + if (typeof error == "string") { + this.exception = new Error(message || ""); + this.exception.name = error; + } else if (!error) { + this.exception = new Error("Error"); + } else { + this.exception = error; } - if (!property && !!object && typeof object == "object") { - for (var prop in object) { - if (typeof object[prop] === "function") { - stub(object, prop); - } - } - - return object; - } - - return sinon.wrapMethod(object, property, wrapper); + return this; } - function getChangingValue(stub, property) { - var index = stub.callCount - 1; - var values = stub[property]; - var prop = index in values ? values[index] : values[values.length - 1]; - stub[property + "Last"] = prop; + function getCallback(behavior, args) { + var callArgAt = behavior.callArgAt; - return prop; - } - - function getCallback(stub, args) { - var callArgAt = getChangingValue(stub, "callArgAts"); - if (callArgAt < 0) { - var callArgProp = getChangingValue(stub, "callArgProps"); + var callArgProp = behavior.callArgProp; for (var i = 0, l = args.length; i < l; ++i) { if (!callArgProp && typeof args[i] == "function") { return args[i]; } @@ -1715,284 +1915,413 @@ } return args[callArgAt]; } - var join = Array.prototype.join; - - function getCallbackError(stub, func, args) { - if (stub.callArgAtsLast < 0) { + function getCallbackError(behavior, func, args) { + if (behavior.callArgAt < 0) { var msg; - if (stub.callArgPropsLast) { - msg = sinon.functionName(stub) + - " expected to yield to '" + stub.callArgPropsLast + - "', but no object with such a property was passed." + if (behavior.callArgProp) { + msg = sinon.functionName(behavior.stub) + + " expected to yield to '" + behavior.callArgProp + + "', but no object with such a property was passed."; } else { - msg = sinon.functionName(stub) + - " expected to yield, but no callback was passed." + msg = sinon.functionName(behavior.stub) + + " expected to yield, but no callback was passed."; } if (args.length > 0) { msg += " Received [" + join.call(args, ", ") + "]"; } return msg; } - return "argument at index " + stub.callArgAtsLast + " is not a function: " + func; + return "argument at index " + behavior.callArgAt + " is not a function: " + func; } - var nextTick = (function () { - if (typeof process === "object" && typeof process.nextTick === "function") { - return process.nextTick; - } else if (typeof setImmediate === "function") { - return setImmediate; - } else { - return function (callback) { - setTimeout(callback, 0); - }; - } - })(); + function callCallback(behavior, args) { + if (typeof behavior.callArgAt == "number") { + var func = getCallback(behavior, args); - function callCallback(stub, args) { - if (stub.callArgAts.length > 0) { - var func = getCallback(stub, args); - if (typeof func != "function") { - throw new TypeError(getCallbackError(stub, func, args)); + throw new TypeError(getCallbackError(behavior, func, args)); } - var callbackArguments = getChangingValue(stub, "callbackArguments"); - var callbackContext = getChangingValue(stub, "callbackContexts"); - - if (stub.callbackAsync) { + if (behavior.callbackAsync) { nextTick(function() { - func.apply(callbackContext, callbackArguments); + func.apply(behavior.callbackContext, behavior.callbackArguments); }); } else { - func.apply(callbackContext, callbackArguments); + func.apply(behavior.callbackContext, behavior.callbackArguments); } } } - var uuid = 0; + proto = { + create: function(stub) { + var behavior = sinon.extend({}, sinon.behavior); + delete behavior.create; + behavior.stub = stub; - sinon.extend(stub, (function () { - var slice = Array.prototype.slice, proto; + return behavior; + }, - function throwsException(error, message) { - if (typeof error == "string") { - this.exception = new Error(message || ""); - this.exception.name = error; - } else if (!error) { - this.exception = new Error("Error"); - } else { - this.exception = error; + isPresent: function() { + return (typeof this.callArgAt == 'number' || + this.exception || + typeof this.returnArgAt == 'number' || + this.returnThis || + this.returnValueDefined); + }, + + invoke: function(context, args) { + callCallback(this, args); + + if (this.exception) { + throw this.exception; + } else if (typeof this.returnArgAt == 'number') { + return args[this.returnArgAt]; + } else if (this.returnThis) { + return context; } + return this.returnValue; + }, + + onCall: function(index) { + return this.stub.onCall(index); + }, + + onFirstCall: function() { + return this.stub.onFirstCall(); + }, + + onSecondCall: function() { + return this.stub.onSecondCall(); + }, + + onThirdCall: function() { + return this.stub.onThirdCall(); + }, + + withArgs: function(/* arguments */) { + throw new Error('Defining a stub by invoking "stub.onCall(...).withArgs(...)" is not supported. ' + + 'Use "stub.withArgs(...).onCall(...)" to define sequential behavior for calls with certain arguments.'); + }, + + callsArg: function callsArg(pos) { + if (typeof pos != "number") { + throw new TypeError("argument index is not number"); + } + + this.callArgAt = pos; + this.callbackArguments = []; + this.callbackContext = undefined; + this.callArgProp = undefined; + this.callbackAsync = false; + return this; - } + }, - proto = { - create: function create() { - var functionStub = function () { + callsArgOn: function callsArgOn(pos, context) { + if (typeof pos != "number") { + throw new TypeError("argument index is not number"); + } + if (typeof context != "object") { + throw new TypeError("argument context is not an object"); + } - callCallback(functionStub, arguments); + this.callArgAt = pos; + this.callbackArguments = []; + this.callbackContext = context; + this.callArgProp = undefined; + this.callbackAsync = false; - if (functionStub.exception) { - throw functionStub.exception; - } else if (typeof functionStub.returnArgAt == 'number') { - return arguments[functionStub.returnArgAt]; - } else if (functionStub.returnThis) { - return this; - } - return functionStub.returnValue; - }; + return this; + }, - functionStub.id = "stub#" + uuid++; - var orig = functionStub; - functionStub = sinon.spy.create(functionStub); - functionStub.func = orig; + callsArgWith: function callsArgWith(pos) { + if (typeof pos != "number") { + throw new TypeError("argument index is not number"); + } - functionStub.callArgAts = []; - functionStub.callbackArguments = []; - functionStub.callbackContexts = []; - functionStub.callArgProps = []; + this.callArgAt = pos; + this.callbackArguments = slice.call(arguments, 1); + this.callbackContext = undefined; + this.callArgProp = undefined; + this.callbackAsync = false; - sinon.extend(functionStub, stub); - functionStub._create = sinon.stub.create; - functionStub.displayName = "stub"; - functionStub.toString = sinon.functionToString; + return this; + }, - return functionStub; - }, + callsArgOnWith: function callsArgWith(pos, context) { + if (typeof pos != "number") { + throw new TypeError("argument index is not number"); + } + if (typeof context != "object") { + throw new TypeError("argument context is not an object"); + } - resetBehavior: function () { - var i; + this.callArgAt = pos; + this.callbackArguments = slice.call(arguments, 2); + this.callbackContext = context; + this.callArgProp = undefined; + this.callbackAsync = false; - this.callArgAts = []; - this.callbackArguments = []; - this.callbackContexts = []; - this.callArgProps = []; + return this; + }, - delete this.returnValue; - delete this.returnArgAt; - this.returnThis = false; + yields: function () { + this.callArgAt = -1; + this.callbackArguments = slice.call(arguments, 0); + this.callbackContext = undefined; + this.callArgProp = undefined; + this.callbackAsync = false; - if (this.fakes) { - for (i = 0; i < this.fakes.length; i++) { - this.fakes[i].resetBehavior(); - } - } - }, + return this; + }, - returns: function returns(value) { - this.returnValue = value; + yieldsOn: function (context) { + if (typeof context != "object") { + throw new TypeError("argument context is not an object"); + } - return this; - }, + this.callArgAt = -1; + this.callbackArguments = slice.call(arguments, 1); + this.callbackContext = context; + this.callArgProp = undefined; + this.callbackAsync = false; - returnsArg: function returnsArg(pos) { - if (typeof pos != "number") { - throw new TypeError("argument index is not number"); - } + return this; + }, - this.returnArgAt = pos; + yieldsTo: function (prop) { + this.callArgAt = -1; + this.callbackArguments = slice.call(arguments, 1); + this.callbackContext = undefined; + this.callArgProp = prop; + this.callbackAsync = false; - return this; - }, + return this; + }, - returnsThis: function returnsThis() { - this.returnThis = true; + yieldsToOn: function (prop, context) { + if (typeof context != "object") { + throw new TypeError("argument context is not an object"); + } - return this; - }, + this.callArgAt = -1; + this.callbackArguments = slice.call(arguments, 2); + this.callbackContext = context; + this.callArgProp = prop; + this.callbackAsync = false; - "throws": throwsException, - throwsException: throwsException, + return this; + }, - callsArg: function callsArg(pos) { - if (typeof pos != "number") { - throw new TypeError("argument index is not number"); - } - this.callArgAts.push(pos); - this.callbackArguments.push([]); - this.callbackContexts.push(undefined); - this.callArgProps.push(undefined); + "throws": throwsException, + throwsException: throwsException, - return this; - }, + returns: function returns(value) { + this.returnValue = value; + this.returnValueDefined = true; - callsArgOn: function callsArgOn(pos, context) { - if (typeof pos != "number") { - throw new TypeError("argument index is not number"); - } - if (typeof context != "object") { - throw new TypeError("argument context is not an object"); - } + return this; + }, - this.callArgAts.push(pos); - this.callbackArguments.push([]); - this.callbackContexts.push(context); - this.callArgProps.push(undefined); + returnsArg: function returnsArg(pos) { + if (typeof pos != "number") { + throw new TypeError("argument index is not number"); + } - return this; - }, + this.returnArgAt = pos; - callsArgWith: function callsArgWith(pos) { - if (typeof pos != "number") { - throw new TypeError("argument index is not number"); - } + return this; + }, - this.callArgAts.push(pos); - this.callbackArguments.push(slice.call(arguments, 1)); - this.callbackContexts.push(undefined); - this.callArgProps.push(undefined); + returnsThis: function returnsThis() { + this.returnThis = true; - return this; - }, + return this; + } + }; - callsArgOnWith: function callsArgWith(pos, context) { - if (typeof pos != "number") { - throw new TypeError("argument index is not number"); - } - if (typeof context != "object") { - throw new TypeError("argument context is not an object"); - } + // create asynchronous versions of callsArg* and yields* methods + for (var method in proto) { + // need to avoid creating anotherasync versions of the newly added async methods + if (proto.hasOwnProperty(method) && + method.match(/^(callsArg|yields)/) && + !method.match(/Async/)) { + proto[method + 'Async'] = (function (syncFnName) { + return function () { + var result = this[syncFnName].apply(this, arguments); + this.callbackAsync = true; + return result; + }; + })(method); + } + } - this.callArgAts.push(pos); - this.callbackArguments.push(slice.call(arguments, 2)); - this.callbackContexts.push(context); - this.callArgProps.push(undefined); + if (commonJSModule) { + module.exports = proto; + } else { + sinon.behavior = proto; + } +}(typeof sinon == "object" && sinon || null)); +/** + * @depend ../sinon.js + * @depend spy.js + * @depend behavior.js + */ +/*jslint eqeqeq: false, onevar: false*/ +/*global module, require, sinon*/ +/** + * Stub functions + * + * @author Christian Johansen (christian@cjohansen.no) + * @license BSD + * + * Copyright (c) 2010-2013 Christian Johansen + */ - return this; - }, +(function (sinon) { + var commonJSModule = typeof module !== 'undefined' && module.exports; - yields: function () { - this.callArgAts.push(-1); - this.callbackArguments.push(slice.call(arguments, 0)); - this.callbackContexts.push(undefined); - this.callArgProps.push(undefined); + if (!sinon && commonJSModule) { + sinon = require("../sinon"); + } - return this; - }, + if (!sinon) { + return; + } - yieldsOn: function (context) { - if (typeof context != "object") { - throw new TypeError("argument context is not an object"); + function stub(object, property, func) { + if (!!func && typeof func != "function") { + throw new TypeError("Custom stub should be function"); + } + + var wrapper; + + if (func) { + wrapper = sinon.spy && sinon.spy.create ? sinon.spy.create(func) : func; + } else { + wrapper = stub.create(); + } + + if (!object && typeof property === "undefined") { + return sinon.stub.create(); + } + + if (typeof property === "undefined" && typeof object == "object") { + for (var prop in object) { + if (typeof object[prop] === "function") { + stub(object, prop); } + } - this.callArgAts.push(-1); - this.callbackArguments.push(slice.call(arguments, 1)); - this.callbackContexts.push(context); - this.callArgProps.push(undefined); + return object; + } - return this; + return sinon.wrapMethod(object, property, wrapper); + } + + function getDefaultBehavior(stub) { + return stub.defaultBehavior || getParentBehaviour(stub) || sinon.behavior.create(stub); + } + + function getParentBehaviour(stub) { + return (stub.parent && getCurrentBehavior(stub.parent)); + } + + function getCurrentBehavior(stub) { + var behavior = stub.behaviors[stub.callCount - 1]; + return behavior && behavior.isPresent() ? behavior : getDefaultBehavior(stub); + } + + var uuid = 0; + + sinon.extend(stub, (function () { + var proto = { + create: function create() { + var functionStub = function () { + return getCurrentBehavior(functionStub).invoke(this, arguments); + }; + + functionStub.id = "stub#" + uuid++; + var orig = functionStub; + functionStub = sinon.spy.create(functionStub); + functionStub.func = orig; + + sinon.extend(functionStub, stub); + functionStub._create = sinon.stub.create; + functionStub.displayName = "stub"; + functionStub.toString = sinon.functionToString; + + functionStub.defaultBehavior = null; + functionStub.behaviors = []; + + return functionStub; }, - yieldsTo: function (prop) { - this.callArgAts.push(-1); - this.callbackArguments.push(slice.call(arguments, 1)); - this.callbackContexts.push(undefined); - this.callArgProps.push(prop); + resetBehavior: function () { + var i; - return this; + this.defaultBehavior = null; + this.behaviors = []; + + delete this.returnValue; + delete this.returnArgAt; + this.returnThis = false; + + if (this.fakes) { + for (i = 0; i < this.fakes.length; i++) { + this.fakes[i].resetBehavior(); + } + } }, - yieldsToOn: function (prop, context) { - if (typeof context != "object") { - throw new TypeError("argument context is not an object"); + onCall: function(index) { + if (!this.behaviors[index]) { + this.behaviors[index] = sinon.behavior.create(this); } - this.callArgAts.push(-1); - this.callbackArguments.push(slice.call(arguments, 2)); - this.callbackContexts.push(context); - this.callArgProps.push(prop); + return this.behaviors[index]; + }, - return this; + onFirstCall: function() { + return this.onCall(0); + }, + + onSecondCall: function() { + return this.onCall(1); + }, + + onThirdCall: function() { + return this.onCall(2); } }; - // create asynchronous versions of callsArg* and yields* methods - for (var method in proto) { - // need to avoid creating anotherasync versions of the newly added async methods - if (proto.hasOwnProperty(method) && - method.match(/^(callsArg|yields|thenYields$)/) && - !method.match(/Async/)) { - proto[method + 'Async'] = (function (syncFnName) { - return function () { - this.callbackAsync = true; - return this[syncFnName].apply(this, arguments); + for (var method in sinon.behavior) { + if (sinon.behavior.hasOwnProperty(method) && + !proto.hasOwnProperty(method) && + method != 'create' && + method != 'withArgs' && + method != 'invoke') { + proto[method] = (function(behaviorMethod) { + return function() { + this.defaultBehavior = this.defaultBehavior || sinon.behavior.create(this); + this.defaultBehavior[behaviorMethod].apply(this.defaultBehavior, arguments); + return this; }; - })(method); + }(method)); } } return proto; - }())); if (commonJSModule) { module.exports = stub; } else { @@ -2014,21 +2343,28 @@ * * Copyright (c) 2010-2013 Christian Johansen */ (function (sinon) { - var commonJSModule = typeof module == "object" && typeof require == "function"; + var commonJSModule = typeof module !== 'undefined' && module.exports; var push = [].push; + var match; if (!sinon && commonJSModule) { sinon = require("../sinon"); } if (!sinon) { return; } + match = sinon.match; + + if (!match && commonJSModule) { + match = require("./match"); + } + function mock(object) { if (!object) { return sinon.expectation.create("Anonymous mock"); } @@ -2205,10 +2541,18 @@ } return expectation.callCount == expectation.maxCalls; } + function verifyMatcher(possibleMatcher, arg){ + if (match && match.isMatcher(possibleMatcher)) { + return possibleMatcher.test(arg); + } else { + return true; + } + } + return { minCalls: 1, maxCalls: 1, create: function create(methodName) { @@ -2314,10 +2658,16 @@ sinon.expectation.fail(this.method + " received too many arguments (" + sinon.format(args) + "), expected " + sinon.format(this.expectedArguments)); } for (var i = 0, l = this.expectedArguments.length; i < l; i += 1) { + + if (!verifyMatcher(this.expectedArguments[i],args[i])) { + sinon.expectation.fail(this.method + " received wrong arguments " + sinon.format(args) + + ", didn't match " + this.expectedArguments.toString()); + } + if (!sinon.deepEqual(this.expectedArguments[i], args[i])) { sinon.expectation.fail(this.method + " received wrong arguments " + sinon.format(args) + ", expected " + sinon.format(this.expectedArguments)); } } @@ -2346,10 +2696,14 @@ args.length != this.expectedArguments.length) { return false; } for (var i = 0, l = this.expectedArguments.length; i < l; i += 1) { + if (!verifyMatcher(this.expectedArguments[i],args[i])) { + return false; + } + if (!sinon.deepEqual(this.expectedArguments[i], args[i])) { return false; } } @@ -2439,11 +2793,11 @@ * * Copyright (c) 2010-2013 Christian Johansen */ (function (sinon) { - var commonJSModule = typeof module == "object" && typeof require == "function"; + var commonJSModule = typeof module !== 'undefined' && module.exports; var push = [].push; var hasOwnProperty = Object.prototype.hasOwnProperty; if (!sinon && commonJSModule) { sinon = require("../sinon"); @@ -2607,10 +2961,14 @@ function addTimer(args, recurring) { if (args.length === 0) { throw new Error("Function requires at least 1 parameter"); } + if (typeof args[0] === "undefined") { + throw new Error("Callback must be provided to timer calls"); + } + var toId = id++; var delay = args[1] || 0; if (!this.timeouts) { this.timeouts = {}; @@ -2708,10 +3066,20 @@ clearInterval: function clearInterval(timerId) { this.clearTimeout(timerId); }, + setImmediate: function setImmediate(callback) { + var passThruArgs = Array.prototype.slice.call(arguments, 1); + + return addTimer.call(this, [callback, 0].concat(passThruArgs), false); + }, + + clearImmediate: function clearImmediate(timerId) { + this.clearTimeout(timerId); + }, + tick: function tick(ms) { ms = typeof ms == "number" ? ms : parseTime(ms); var tickFrom = this.now, tickTo = this.now + ms, previous = this.now; var timer = this.firstTimerInRange(tickFrom, tickTo); @@ -2738,19 +3106,19 @@ return this.now; }, firstTimerInRange: function (from, to) { - var timer, smallest, originalTimer; + var timer, smallest = null, originalTimer; for (var id in this.timeouts) { if (this.timeouts.hasOwnProperty(id)) { if (this.timeouts[id].callAt < from || this.timeouts[id].callAt > to) { continue; } - if (!smallest || this.timeouts[id].callAt < smallest) { + if (smallest === null || this.timeouts[id].callAt < smallest) { originalTimer = this.timeouts[id]; smallest = this.timeouts[id].callAt; timer = { func: this.timeouts[id].func, @@ -2852,25 +3220,43 @@ target.prototype = source.prototype; target.parse = source.parse; target.UTC = source.UTC; target.prototype.toUTCString = source.prototype.toUTCString; + + for (var prop in source) { + if (source.hasOwnProperty(prop)) { + target[prop] = source[prop]; + } + } + return target; } var methods = ["Date", "setTimeout", "setInterval", "clearTimeout", "clearInterval"]; + if (typeof global.setImmediate !== "undefined") { + methods.push("setImmediate"); + } + + if (typeof global.clearImmediate !== "undefined") { + methods.push("clearImmediate"); + } + function restore() { var method; for (var i = 0, l = this.methods.length; i < l; i++) { method = this.methods[i]; + if (global[method].hadOwnProperty) { global[method] = this["_" + method]; } else { - delete global[method]; + try { + delete global[method]; + } catch (e) {} } } // Prevent multiple executions which will completely remove these props this.methods = []; @@ -2917,16 +3303,18 @@ }(typeof global != "undefined" && typeof global !== "function" ? global : this)); sinon.timers = { setTimeout: setTimeout, clearTimeout: clearTimeout, + setImmediate: (typeof setImmediate !== "undefined" ? setImmediate : undefined), + clearImmediate: (typeof clearImmediate !== "undefined" ? clearImmediate: undefined), setInterval: setInterval, clearInterval: clearInterval, Date: Date }; -if (typeof module == "object" && typeof require == "function") { +if (typeof module !== 'undefined' && module.exports) { module.exports = sinon; } /*jslint eqeqeq: false, onevar: false*/ /*global sinon, module, require, ActiveXObject, XMLHttpRequest, DOMParser*/ @@ -2967,18 +3355,37 @@ preventDefault: function () { this.defaultPrevented = true; } }; + sinon.ProgressEvent = function ProgressEvent(type, progressEventRaw, target) { + this.initEvent(type, false, false, target); + this.loaded = progressEventRaw.loaded || null; + this.total = progressEventRaw.total || null; + }; + + sinon.ProgressEvent.prototype = new sinon.Event(); + + sinon.ProgressEvent.prototype.constructor = sinon.ProgressEvent; + + sinon.CustomEvent = function CustomEvent(type, customData, target) { + this.initEvent(type, false, false, target); + this.detail = customData.detail || null; + }; + + sinon.CustomEvent.prototype = new sinon.Event(); + + sinon.CustomEvent.prototype.constructor = sinon.CustomEvent; + sinon.EventTarget = { - addEventListener: function addEventListener(event, listener, useCapture) { + addEventListener: function addEventListener(event, listener) { this.eventListeners = this.eventListeners || {}; this.eventListeners[event] = this.eventListeners[event] || []; push.call(this.eventListeners[event], listener); }, - removeEventListener: function removeEventListener(event, listener, useCapture) { + removeEventListener: function removeEventListener(event, listener) { var listeners = this.eventListeners && this.eventListeners[event] || []; for (var i = 0, l = listeners.length; i < l; ++i) { if (listeners[i] == listener) { return listeners.splice(i, 1); @@ -3016,24 +3423,27 @@ * @license BSD * * Copyright (c) 2010-2013 Christian Johansen */ -if (typeof sinon == "undefined") { - this.sinon = {}; -} -sinon.xhr = { XMLHttpRequest: this.XMLHttpRequest }; - // wrapper for global (function(global) { + if (typeof sinon === "undefined") { + global.sinon = {}; + } + + var supportsProgress = typeof ProgressEvent !== "undefined"; + var supportsCustomEvent = typeof CustomEvent !== "undefined"; + sinon.xhr = { XMLHttpRequest: global.XMLHttpRequest }; var xhr = sinon.xhr; xhr.GlobalXMLHttpRequest = global.XMLHttpRequest; xhr.GlobalActiveXObject = global.ActiveXObject; xhr.supportsActiveX = typeof xhr.GlobalActiveXObject != "undefined"; xhr.supportsXHR = typeof xhr.GlobalXMLHttpRequest != "undefined"; xhr.workingXHR = xhr.supportsXHR ? xhr.GlobalXMLHttpRequest : xhr.supportsActiveX ? function() { return new xhr.GlobalActiveXObject("MSXML2.XMLHTTP.3.0") } : false; + xhr.supportsCORS = 'withCredentials' in (new sinon.xhr.GlobalXMLHttpRequest()); /*jsl:ignore*/ var unsafeHeaders = { "Accept-Charset": true, "Accept-Encoding": true, @@ -3060,20 +3470,25 @@ this.readyState = FakeXMLHttpRequest.UNSENT; this.requestHeaders = {}; this.requestBody = null; this.status = 0; this.statusText = ""; + this.upload = new UploadProgress(); + if (sinon.xhr.supportsCORS) { + this.withCredentials = false; + } + var xhr = this; var events = ["loadstart", "load", "abort", "loadend"]; function addEventListener(eventName) { xhr.addEventListener(eventName, function (event) { var listener = xhr["on" + eventName]; if (listener && typeof listener == "function") { - listener(event); + listener.call(this, event); } }); } for (var i = events.length - 1; i >= 0; i--) { @@ -3083,10 +3498,45 @@ if (typeof FakeXMLHttpRequest.onCreate == "function") { FakeXMLHttpRequest.onCreate(this); } } + // An upload object is created for each + // FakeXMLHttpRequest and allows upload + // events to be simulated using uploadProgress + // and uploadError. + function UploadProgress() { + this.eventListeners = { + "progress": [], + "load": [], + "abort": [], + "error": [] + } + } + + UploadProgress.prototype.addEventListener = function(event, listener) { + this.eventListeners[event].push(listener); + }; + + UploadProgress.prototype.removeEventListener = function(event, listener) { + var listeners = this.eventListeners[event] || []; + + for (var i = 0, l = listeners.length; i < l; ++i) { + if (listeners[i] == listener) { + return listeners.splice(i, 1); + } + } + }; + + UploadProgress.prototype.dispatchEvent = function(event) { + var listeners = this.eventListeners[event.type] || []; + + for (var i = 0, listener; (listener = listeners[i]) != null; i++) { + listener(event); + } + }; + function verifyState(xhr) { if (xhr.readyState !== FakeXMLHttpRequest.OPENED) { throw new Error("INVALID_STATE_ERR"); } @@ -3104,11 +3554,11 @@ } } function some(collection, callback) { for (var index = 0; index < collection.length; index++) { if(callback(collection[index]) === true) return true; - }; + } return false; } // largest arity in XHR is 5 - XHR#open var apply = function(obj,method,args) { switch(args.length) { @@ -3116,11 +3566,11 @@ case 1: return obj[method](args[0]); case 2: return obj[method](args[0],args[1]); case 3: return obj[method](args[0],args[1],args[2]); case 4: return obj[method](args[0],args[1],args[2],args[3]); case 5: return obj[method](args[0],args[1],args[2],args[3],args[4]); - }; + } }; FakeXMLHttpRequest.filters = []; FakeXMLHttpRequest.addFilter = function(fn) { this.filters.push(fn) @@ -3155,11 +3605,11 @@ copyAttrs(["responseText"]); } if(xhr.readyState === FakeXMLHttpRequest.DONE) { copyAttrs(["responseXML"]); } - if(fakeXhr.onreadystatechange) fakeXhr.onreadystatechange.call(fakeXhr); + if(fakeXhr.onreadystatechange) fakeXhr.onreadystatechange.call(fakeXhr, { target: fakeXhr }); }; if(xhr.addEventListener) { for(var event in fakeXhr.eventListeners) { if(fakeXhr.eventListeners.hasOwnProperty(event)) { each(fakeXhr.eventListeners[event],function(handler) { @@ -3173,10 +3623,16 @@ } apply(xhr,"open",xhrArgs); }; FakeXMLHttpRequest.useFilters = false; + function verifyRequestOpened(xhr) { + if (xhr.readyState != FakeXMLHttpRequest.OPENED) { + throw new Error("INVALID_STATE_ERR - " + xhr.readyState); + } + } + function verifyRequestSent(xhr) { if (xhr.readyState == FakeXMLHttpRequest.DONE) { throw new Error("Request done"); } } @@ -3236,10 +3692,14 @@ switch (this.readyState) { case FakeXMLHttpRequest.DONE: this.dispatchEvent(new sinon.Event("load", false, false, this)); this.dispatchEvent(new sinon.Event("loadend", false, false, this)); + this.upload.dispatchEvent(new sinon.Event("load", false, false, this)); + if (supportsProgress) { + this.upload.dispatchEvent(new sinon.ProgressEvent('progress', {loaded: 100, total: 100})); + } break; } }, setRequestHeader: function setRequestHeader(header, value) { @@ -3256,10 +3716,11 @@ } }, // Helps testing setResponseHeaders: function setResponseHeaders(headers) { + verifyRequestOpened(this); this.responseHeaders = {}; for (var header in headers) { if (headers.hasOwnProperty(header)) { this.responseHeaders[header] = headers[header]; @@ -3311,10 +3772,13 @@ } this.readyState = sinon.FakeXMLHttpRequest.UNSENT; this.dispatchEvent(new sinon.Event("abort", false, false, this)); + + this.upload.dispatchEvent(new sinon.Event("abort", false, false, this)); + if (typeof this.onerror === "function") { this.onerror(); } }, @@ -3390,18 +3854,26 @@ this.readyState = FakeXMLHttpRequest.DONE; } }, respond: function respond(status, headers, body) { - this.setResponseHeaders(headers || {}); this.status = typeof status == "number" ? status : 200; this.statusText = FakeXMLHttpRequest.statusCodes[this.status]; + this.setResponseHeaders(headers || {}); this.setResponseBody(body || ""); - if (typeof this.onload === "function"){ - this.onload(); + }, + + uploadProgress: function uploadProgress(progressEventRaw) { + if (supportsProgress) { + this.upload.dispatchEvent(new sinon.ProgressEvent("progress", progressEventRaw)); } + }, + uploadError: function uploadError(error) { + if (supportsCustomEvent) { + this.upload.dispatchEvent(new sinon.CustomEvent("error", {"detail": error})); + } } }); sinon.extend(FakeXMLHttpRequest, { UNSENT: 0, @@ -3504,13 +3976,14 @@ return sinon.FakeXMLHttpRequest; }; sinon.FakeXMLHttpRequest = FakeXMLHttpRequest; -})(this); -if (typeof module == "object" && typeof require == "function") { +})(typeof global === "object" ? global : this); + +if (typeof module !== 'undefined' && module.exports) { module.exports = sinon; } /** * @depend fake_xml_http_request.js @@ -3568,21 +4041,20 @@ return matchMethod && matchUrl; } function match(response, request) { - var requestMethod = this.getHTTPMethod(request); var requestUrl = request.url; if (!/^https?:\/\//.test(requestUrl) || rCurrLoc.test(requestUrl)) { requestUrl = requestUrl.replace(rCurrLoc, ""); } if (matchOne(response, this.getHTTPMethod(request), requestUrl)) { if (typeof response.response == "function") { var ru = response.url; - var args = [request].concat(!ru ? [] : requestUrl.match(ru).slice(1)); + var args = [request].concat(ru && typeof ru.exec == "function" ? ru.exec(requestUrl).slice(1) : []); return response.response.apply(response, args); } return true; } @@ -3616,20 +4088,20 @@ var server = this; push.call(this.requests, xhrObj); xhrObj.onSend = function () { server.handleRequest(this); - }; - if (this.autoRespond && !this.responding) { - setTimeout(function () { - server.responding = false; - server.respond(); - }, this.autoRespondAfter || 10); + if (server.autoRespond && !server.responding) { + setTimeout(function () { + server.responding = false; + server.respond(); + }, server.autoRespondAfter || 10); - this.responding = true; - } + server.responding = true; + } + }; }, getHTTPMethod: function getHTTPMethod(request) { if (this.fakeHTTPMethods && /post/i.test(request.method)) { var matches = (request.requestBody || "").match(/_method=([^\b;]+)/); @@ -3678,13 +4150,14 @@ }, respond: function respond() { if (arguments.length > 0) this.respondWith.apply(this, arguments); var queue = this.queue || []; + var requests = queue.splice(0); var request; - while(request = queue.shift()) { + while(request = requests.shift()) { this.processRequest(request); } }, processRequest: function processRequest(request) { @@ -3694,11 +4167,11 @@ } var response = this.response || [404, {}, ""]; if (this.responses) { - for (var i = 0, l = this.responses.length; i < l; i++) { + for (var l = this.responses.length, i = l - 1; i >= 0; i--) { if (match.call(this, this.responses[i], request)) { response = this.responses[i].response; break; } } @@ -3718,11 +4191,11 @@ return this.xhr.restore && this.xhr.restore.apply(this.xhr, arguments); } }; }()); -if (typeof module == "object" && typeof require == "function") { +if (typeof module !== 'undefined' && module.exports) { module.exports = sinon; } /** * @depend fake_server.js @@ -3823,11 +4296,11 @@ * @license BSD * * Copyright (c) 2010-2013 Christian Johansen */ -if (typeof module == "object" && typeof require == "function") { +if (typeof module !== 'undefined' && module.exports) { var sinon = require("../sinon"); sinon.extend(sinon, require("./util/fake_timers")); } (function () { @@ -3836,12 +4309,13 @@ function exposeValue(sandbox, config, key, value) { if (!value) { return; } - if (config.injectInto) { + if (config.injectInto && !(key in config.injectInto)) { config.injectInto[key] = value; + sandbox.injectedKeys.push(key); } else { push.call(sandbox.args, value); } } @@ -3900,17 +4374,33 @@ } return obj; }, + restore: function () { + sinon.collection.restore.apply(this, arguments); + this.restoreContext(); + }, + + restoreContext: function () { + if (this.injectedKeys) { + for (var i = 0, j = this.injectedKeys.length; i < j; i++) { + delete this.injectInto[this.injectedKeys[i]]; + } + this.injectedKeys = []; + } + }, + create: function (config) { if (!config) { return sinon.create(sinon.sandbox); } var sandbox = prepareSandboxFromConfig(config); sandbox.args = sandbox.args || []; + sandbox.injectedKeys = []; + sandbox.injectInto = config.injectInto; var prop, value, exposed = sandbox.inject({}); if (config.properties) { for (var i = 0, l = config.properties.length; i < l; i++) { prop = config.properties[i]; @@ -3925,11 +4415,11 @@ } }); sinon.sandbox.useFakeXMLHttpRequest = sinon.sandbox.useFakeServer; - if (typeof module == "object" && typeof require == "function") { + if (typeof module !== 'undefined' && module.exports) { module.exports = sinon.sandbox; } }()); /** @@ -3948,11 +4438,11 @@ * * Copyright (c) 2010-2013 Christian Johansen */ (function (sinon) { - var commonJSModule = typeof module == "object" && typeof require == "function"; + var commonJSModule = typeof module !== 'undefined' && module.exports; if (!sinon && commonJSModule) { sinon = require("../sinon"); } @@ -4021,11 +4511,11 @@ * * Copyright (c) 2010-2013 Christian Johansen */ (function (sinon) { - var commonJSModule = typeof module == "object" && typeof require == "function"; + var commonJSModule = typeof module !== 'undefined' && module.exports; if (!sinon && commonJSModule) { sinon = require("../sinon"); } @@ -4118,11 +4608,11 @@ * * Copyright (c) 2010-2013 Christian Johansen */ (function (sinon, global) { - var commonJSModule = typeof module == "object" && typeof require == "function"; + var commonJSModule = typeof module !== "undefined" && module.exports; var slice = Array.prototype.slice; var assert; if (!sinon && commonJSModule) { sinon = require("../sinon"); @@ -4186,11 +4676,11 @@ } function exposedName(prefix, prop) { return !prefix || /^fail/.test(prop) ? prop : prefix + prop.slice(0, 1).toUpperCase() + prop.slice(1); - }; + } assert = { failException: "AssertError", fail: function fail(message) { @@ -4254,10 +4744,24 @@ target[exposedName(prefix, method)] = this[method]; } } return target; + }, + + match: function match(actual, expectation) { + var matcher = sinon.match(expectation); + if (matcher.test(actual)) { + assert.pass("match"); + } else { + var formatted = [ + "expected value to match", + " expected = " + sinon.format(expectation), + " actual = " + sinon.format(actual) + ] + failAssertion(this, formatted.join("\n")); + } } }; mirrorPropAsAssertion("called", "expected %n to have been called at least once but was never called"); mirrorPropAsAssertion("notCalled", function (spy) { return !spy.called; }, @@ -4285,6 +4789,6 @@ } else { sinon.assert = assert; } }(typeof sinon == "object" && sinon || null, typeof window != "undefined" ? window : (typeof self != "undefined") ? self : global)); -return sinon;}.call(typeof window != 'undefined' && window || {})); \ No newline at end of file +return sinon;}.call(typeof window != 'undefined' && window || {}));