171 lines
5.3 KiB
JavaScript
171 lines
5.3 KiB
JavaScript
"use strict";
|
|
|
|
var arrayProto = require("@sinonjs/commons").prototypes.array;
|
|
var createProxy = require("./proxy");
|
|
var extend = require("./util/core/extend");
|
|
var functionName = require("@sinonjs/commons").functionName;
|
|
var getPropertyDescriptor = require("./util/core/get-property-descriptor");
|
|
var deepEqual = require("@sinonjs/samsam").deepEqual;
|
|
var isEsModule = require("./util/core/is-es-module");
|
|
var proxyCallUtil = require("./proxy-call-util");
|
|
var walkObject = require("./util/core/walk-object");
|
|
var wrapMethod = require("./util/core/wrap-method");
|
|
var valueToString = require("@sinonjs/commons").valueToString;
|
|
|
|
/* cache references to library methods so that they also can be stubbed without problems */
|
|
var forEach = arrayProto.forEach;
|
|
var pop = arrayProto.pop;
|
|
var push = arrayProto.push;
|
|
var slice = arrayProto.slice;
|
|
var filter = Array.prototype.filter;
|
|
|
|
var uuid = 0;
|
|
|
|
function matches(fake, args, strict) {
|
|
var margs = fake.matchingArguments;
|
|
if (margs.length <= args.length && deepEqual(slice(args, 0, margs.length), margs)) {
|
|
return !strict || margs.length === args.length;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Public API
|
|
var spyApi = {
|
|
withArgs: function() {
|
|
var args = slice(arguments);
|
|
var matching = pop(this.matchingFakes(args, true));
|
|
if (matching) {
|
|
return matching;
|
|
}
|
|
|
|
var original = this;
|
|
var fake = this.instantiateFake();
|
|
fake.matchingArguments = args;
|
|
fake.parent = this;
|
|
push(this.fakes, fake);
|
|
|
|
fake.withArgs = function() {
|
|
return original.withArgs.apply(original, arguments);
|
|
};
|
|
|
|
forEach(original.args, function(arg, i) {
|
|
if (!matches(fake, arg)) {
|
|
return;
|
|
}
|
|
|
|
proxyCallUtil.incrementCallCount(fake);
|
|
push(fake.thisValues, original.thisValues[i]);
|
|
push(fake.args, arg);
|
|
push(fake.returnValues, original.returnValues[i]);
|
|
push(fake.exceptions, original.exceptions[i]);
|
|
push(fake.callIds, original.callIds[i]);
|
|
});
|
|
|
|
proxyCallUtil.createCallProperties(fake);
|
|
|
|
return fake;
|
|
},
|
|
|
|
// Override proxy default implementation
|
|
matchingFakes: function(args, strict) {
|
|
return filter.call(this.fakes, function(fake) {
|
|
return matches(fake, args, strict);
|
|
});
|
|
}
|
|
};
|
|
|
|
/* eslint-disable local-rules/no-prototype-methods */
|
|
var delegateToCalls = proxyCallUtil.delegateToCalls;
|
|
delegateToCalls(spyApi, "callArg", false, "callArgWith", true, function() {
|
|
throw new Error(this.toString() + " cannot call arg since it was not yet invoked.");
|
|
});
|
|
spyApi.callArgWith = spyApi.callArg;
|
|
delegateToCalls(spyApi, "callArgOn", false, "callArgOnWith", true, function() {
|
|
throw new Error(this.toString() + " cannot call arg since it was not yet invoked.");
|
|
});
|
|
spyApi.callArgOnWith = spyApi.callArgOn;
|
|
delegateToCalls(spyApi, "throwArg", false, "throwArg", false, function() {
|
|
throw new Error(this.toString() + " cannot throw arg since it was not yet invoked.");
|
|
});
|
|
delegateToCalls(spyApi, "yield", false, "yield", true, function() {
|
|
throw new Error(this.toString() + " cannot yield since it was not yet invoked.");
|
|
});
|
|
// "invokeCallback" is an alias for "yield" since "yield" is invalid in strict mode.
|
|
spyApi.invokeCallback = spyApi.yield;
|
|
delegateToCalls(spyApi, "yieldOn", false, "yieldOn", true, function() {
|
|
throw new Error(this.toString() + " cannot yield since it was not yet invoked.");
|
|
});
|
|
delegateToCalls(spyApi, "yieldTo", false, "yieldTo", true, function(property) {
|
|
throw new Error(
|
|
this.toString() + " cannot yield to '" + valueToString(property) + "' since it was not yet invoked."
|
|
);
|
|
});
|
|
delegateToCalls(spyApi, "yieldToOn", false, "yieldToOn", true, function(property) {
|
|
throw new Error(
|
|
this.toString() + " cannot yield to '" + valueToString(property) + "' since it was not yet invoked."
|
|
);
|
|
});
|
|
/* eslint-enable local-rules/no-prototype-methods */
|
|
|
|
function createSpy(func) {
|
|
var name;
|
|
var funk = func;
|
|
|
|
if (typeof funk !== "function") {
|
|
funk = function() {
|
|
return;
|
|
};
|
|
} else {
|
|
name = functionName(funk);
|
|
}
|
|
|
|
var proxy = createProxy(funk, funk);
|
|
|
|
// Inherit spy API:
|
|
extend.nonEnum(proxy, spyApi);
|
|
extend.nonEnum(proxy, {
|
|
displayName: name || "spy",
|
|
fakes: [],
|
|
instantiateFake: createSpy,
|
|
id: "spy#" + uuid++
|
|
});
|
|
return proxy;
|
|
}
|
|
|
|
function spy(object, property, types) {
|
|
var descriptor, methodDesc;
|
|
|
|
if (isEsModule(object)) {
|
|
throw new TypeError("ES Modules cannot be spied");
|
|
}
|
|
|
|
if (!property && typeof object === "function") {
|
|
return createSpy(object);
|
|
}
|
|
|
|
if (!property && typeof object === "object") {
|
|
return walkObject(spy, object);
|
|
}
|
|
|
|
if (!object && !property) {
|
|
return createSpy(function() {
|
|
return;
|
|
});
|
|
}
|
|
|
|
if (!types) {
|
|
return wrapMethod(object, property, createSpy(object[property]));
|
|
}
|
|
|
|
descriptor = {};
|
|
methodDesc = getPropertyDescriptor(object, property);
|
|
|
|
forEach(types, function(type) {
|
|
descriptor[type] = createSpy(methodDesc[type]);
|
|
});
|
|
|
|
return wrapMethod(object, property, descriptor);
|
|
}
|
|
|
|
extend(spy, spyApi);
|
|
module.exports = spy;
|