231 lines
7.0 KiB
JavaScript
231 lines
7.0 KiB
JavaScript
"use strict";
|
|
|
|
var arrayProto = require("@sinonjs/commons").prototypes.array;
|
|
var extend = require("./util/core/extend");
|
|
var functionName = require("@sinonjs/commons").functionName;
|
|
var nextTick = require("./util/core/next-tick");
|
|
var valueToString = require("@sinonjs/commons").valueToString;
|
|
var exportAsyncBehaviors = require("./util/core/export-async-behaviors");
|
|
|
|
var concat = arrayProto.concat;
|
|
var join = arrayProto.join;
|
|
var reverse = arrayProto.reverse;
|
|
var slice = arrayProto.slice;
|
|
|
|
var useLeftMostCallback = -1;
|
|
var useRightMostCallback = -2;
|
|
|
|
function getCallback(behavior, args) {
|
|
var callArgAt = behavior.callArgAt;
|
|
|
|
if (callArgAt >= 0) {
|
|
return args[callArgAt];
|
|
}
|
|
|
|
var argumentList;
|
|
|
|
if (callArgAt === useLeftMostCallback) {
|
|
argumentList = args;
|
|
}
|
|
|
|
if (callArgAt === useRightMostCallback) {
|
|
argumentList = reverse(slice(args));
|
|
}
|
|
|
|
var callArgProp = behavior.callArgProp;
|
|
|
|
for (var i = 0, l = argumentList.length; i < l; ++i) {
|
|
if (!callArgProp && typeof argumentList[i] === "function") {
|
|
return argumentList[i];
|
|
}
|
|
|
|
if (callArgProp && argumentList[i] && typeof argumentList[i][callArgProp] === "function") {
|
|
return argumentList[i][callArgProp];
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function getCallbackError(behavior, func, args) {
|
|
if (behavior.callArgAt < 0) {
|
|
var msg;
|
|
|
|
if (behavior.callArgProp) {
|
|
msg =
|
|
functionName(behavior.stub) +
|
|
" expected to yield to '" +
|
|
valueToString(behavior.callArgProp) +
|
|
"', but no object with such a property was passed.";
|
|
} else {
|
|
msg = functionName(behavior.stub) + " expected to yield, but no callback was passed.";
|
|
}
|
|
|
|
if (args.length > 0) {
|
|
msg += " Received [" + join(args, ", ") + "]";
|
|
}
|
|
|
|
return msg;
|
|
}
|
|
|
|
return "argument at index " + behavior.callArgAt + " is not a function: " + func;
|
|
}
|
|
|
|
function ensureArgs(name, behavior, args) {
|
|
// map function name to internal property
|
|
// callsArg => callArgAt
|
|
var property = name.replace(/sArg/, "ArgAt");
|
|
var index = behavior[property];
|
|
|
|
if (index >= args.length) {
|
|
throw new TypeError(
|
|
name + " failed: " + (index + 1) + " arguments required but only " + args.length + " present"
|
|
);
|
|
}
|
|
}
|
|
|
|
function callCallback(behavior, args) {
|
|
if (typeof behavior.callArgAt === "number") {
|
|
ensureArgs("callsArg", behavior, args);
|
|
var func = getCallback(behavior, args);
|
|
|
|
if (typeof func !== "function") {
|
|
throw new TypeError(getCallbackError(behavior, func, args));
|
|
}
|
|
|
|
if (behavior.callbackAsync) {
|
|
nextTick(function() {
|
|
func.apply(behavior.callbackContext, behavior.callbackArguments);
|
|
});
|
|
} else {
|
|
return func.apply(behavior.callbackContext, behavior.callbackArguments);
|
|
}
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
var proto = {
|
|
create: function create(stub) {
|
|
var behavior = extend({}, proto);
|
|
delete behavior.create;
|
|
delete behavior.addBehavior;
|
|
delete behavior.createBehavior;
|
|
behavior.stub = stub;
|
|
|
|
if (stub.defaultBehavior && stub.defaultBehavior.promiseLibrary) {
|
|
behavior.promiseLibrary = stub.defaultBehavior.promiseLibrary;
|
|
}
|
|
|
|
return behavior;
|
|
},
|
|
|
|
isPresent: function isPresent() {
|
|
return (
|
|
typeof this.callArgAt === "number" ||
|
|
this.exception ||
|
|
this.exceptionCreator ||
|
|
typeof this.returnArgAt === "number" ||
|
|
this.returnThis ||
|
|
typeof this.resolveArgAt === "number" ||
|
|
this.resolveThis ||
|
|
typeof this.throwArgAt === "number" ||
|
|
this.fakeFn ||
|
|
this.returnValueDefined
|
|
);
|
|
},
|
|
|
|
invoke: function invoke(context, args) {
|
|
/*
|
|
* callCallback (conditionally) calls ensureArgs
|
|
*
|
|
* Note: callCallback intentionally happens before
|
|
* everything else and cannot be moved lower
|
|
*/
|
|
var returnValue = callCallback(this, args);
|
|
|
|
if (this.exception) {
|
|
throw this.exception;
|
|
} else if (this.exceptionCreator) {
|
|
this.exception = this.exceptionCreator();
|
|
this.exceptionCreator = undefined;
|
|
throw this.exception;
|
|
} else if (typeof this.returnArgAt === "number") {
|
|
ensureArgs("returnsArg", this, args);
|
|
return args[this.returnArgAt];
|
|
} else if (this.returnThis) {
|
|
return context;
|
|
} else if (typeof this.throwArgAt === "number") {
|
|
ensureArgs("throwsArg", this, args);
|
|
throw args[this.throwArgAt];
|
|
} else if (this.fakeFn) {
|
|
return this.fakeFn.apply(context, args);
|
|
} else if (typeof this.resolveArgAt === "number") {
|
|
ensureArgs("resolvesArg", this, args);
|
|
return (this.promiseLibrary || Promise).resolve(args[this.resolveArgAt]);
|
|
} else if (this.resolveThis) {
|
|
return (this.promiseLibrary || Promise).resolve(context);
|
|
} else if (this.resolve) {
|
|
return (this.promiseLibrary || Promise).resolve(this.returnValue);
|
|
} else if (this.reject) {
|
|
return (this.promiseLibrary || Promise).reject(this.returnValue);
|
|
} else if (this.callsThrough) {
|
|
return this.stub.wrappedMethod.apply(context, args);
|
|
} else if (typeof this.returnValue !== "undefined") {
|
|
return this.returnValue;
|
|
} else if (typeof this.callArgAt === "number") {
|
|
return returnValue;
|
|
}
|
|
|
|
return this.returnValue;
|
|
},
|
|
|
|
onCall: function onCall(index) {
|
|
return this.stub.onCall(index);
|
|
},
|
|
|
|
onFirstCall: function onFirstCall() {
|
|
return this.stub.onFirstCall();
|
|
},
|
|
|
|
onSecondCall: function onSecondCall() {
|
|
return this.stub.onSecondCall();
|
|
},
|
|
|
|
onThirdCall: function onThirdCall() {
|
|
return this.stub.onThirdCall();
|
|
},
|
|
|
|
withArgs: function withArgs(/* 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."
|
|
);
|
|
}
|
|
};
|
|
|
|
function createBehavior(behaviorMethod) {
|
|
return function() {
|
|
this.defaultBehavior = this.defaultBehavior || proto.create(this);
|
|
this.defaultBehavior[behaviorMethod].apply(this.defaultBehavior, arguments);
|
|
return this;
|
|
};
|
|
}
|
|
|
|
function addBehavior(stub, name, fn) {
|
|
proto[name] = function() {
|
|
fn.apply(this, concat([this], slice(arguments)));
|
|
return this.stub || this;
|
|
};
|
|
|
|
stub[name] = createBehavior(name);
|
|
}
|
|
|
|
proto.addBehavior = addBehavior;
|
|
proto.createBehavior = createBehavior;
|
|
|
|
var asyncBehaviors = exportAsyncBehaviors(proto);
|
|
|
|
module.exports = extend.nonEnum({}, proto, asyncBehaviors);
|