| "use strict"; |
| |
| const arrayProto = require("@sinonjs/commons").prototypes.array; |
| const extend = require("./util/core/extend"); |
| const functionName = require("@sinonjs/commons").functionName; |
| const nextTick = require("./util/core/next-tick"); |
| const valueToString = require("@sinonjs/commons").valueToString; |
| const exportAsyncBehaviors = require("./util/core/export-async-behaviors"); |
| |
| const concat = arrayProto.concat; |
| const join = arrayProto.join; |
| const reverse = arrayProto.reverse; |
| const slice = arrayProto.slice; |
| |
| const useLeftMostCallback = -1; |
| const useRightMostCallback = -2; |
| |
| function getCallback(behavior, args) { |
| const callArgAt = behavior.callArgAt; |
| |
| if (callArgAt >= 0) { |
| return args[callArgAt]; |
| } |
| |
| let argumentList; |
| |
| if (callArgAt === useLeftMostCallback) { |
| argumentList = args; |
| } |
| |
| if (callArgAt === useRightMostCallback) { |
| argumentList = reverse(slice(args)); |
| } |
| |
| const callArgProp = behavior.callArgProp; |
| |
| for (let 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) { |
| let 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 |
| const property = name.replace(/sArg/, "ArgAt"); |
| const 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); |
| const 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; |
| } |
| |
| const proto = { |
| create: function create(stub) { |
| const 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 |
| ); |
| }, |
| |
| /*eslint complexity: ["error", 20]*/ |
| invoke: function invoke(context, args) { |
| /* |
| * callCallback (conditionally) calls ensureArgs |
| * |
| * Note: callCallback intentionally happens before |
| * everything else and cannot be moved lower |
| */ |
| const 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) { |
| const wrappedMethod = this.effectiveWrappedMethod(); |
| |
| return wrappedMethod.apply(context, args); |
| } else if (this.callsThroughWithNew) { |
| // Get the original method (assumed to be a constructor in this case) |
| const WrappedClass = this.effectiveWrappedMethod(); |
| // Turn the arguments object into a normal array |
| const argsArray = slice(args); |
| // Call the constructor |
| const F = WrappedClass.bind.apply( |
| WrappedClass, |
| concat([null], argsArray), |
| ); |
| return new F(); |
| } else if (typeof this.returnValue !== "undefined") { |
| return this.returnValue; |
| } else if (typeof this.callArgAt === "number") { |
| return returnValue; |
| } |
| |
| return this.returnValue; |
| }, |
| |
| effectiveWrappedMethod: function effectiveWrappedMethod() { |
| for (let stubb = this.stub; stubb; stubb = stubb.parent) { |
| if (stubb.wrappedMethod) { |
| return stubb.wrappedMethod; |
| } |
| } |
| throw new Error("Unable to find wrapped method"); |
| }, |
| |
| 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; |
| |
| const asyncBehaviors = exportAsyncBehaviors(proto); |
| |
| module.exports = extend.nonEnum({}, proto, asyncBehaviors); |