sinon
Version:
JavaScript test spies, stubs and mocks.
208 lines (177 loc) • 6.03 kB
JavaScript
;
var commons = require('@sinonjs/commons');
var samsam = require('@sinonjs/samsam');
var proxy = require('./proxy.js');
var extend = require('./util/core/extend.js');
var getPropertyDescriptor = require('./util/core/get-property-descriptor.js');
var isEsModule = require('./util/core/is-es-module.js');
var proxyCallUtil = require('./proxy-call-util.js');
var walkObject = require('./util/core/walk-object.js');
var wrapMethod = require('./util/core/wrap-method.js');
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
var commons__default = /*#__PURE__*/_interopDefault(commons);
var samsam__default = /*#__PURE__*/_interopDefault(samsam);
const { prototypes, functionName, valueToString } = commons__default.default;
const { deepEqual } = samsam__default.default;
const { forEach, pop, push, slice } = prototypes.array;
const filter = Array.prototype.filter;
/**
* @callback SinonFunction
* @param {...unknown} args
* @returns {unknown}
*/
let uuid = 0;
function matches(fake, args, strict) {
const 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
const spyApi = {
withArgs: function () {
const args = slice(arguments);
const matching = pop(this.matchingFakes(args, true));
if (matching) {
return matching;
}
const original = this;
const fakeInstance = this.instantiateFake();
fakeInstance.matchingArguments = args;
fakeInstance.parent = this;
push(this.fakes, fakeInstance);
fakeInstance.withArgs = function () {
return original.withArgs.apply(original, arguments);
};
forEach(original.args, function (arg, i) {
if (!matches(fakeInstance, arg)) {
return;
}
proxyCallUtil.incrementCallCount(fakeInstance);
push(fakeInstance.thisValues, original.thisValues[i]);
push(fakeInstance.args, arg);
push(fakeInstance.returnValues, original.returnValues[i]);
push(fakeInstance.exceptions, original.exceptions[i]);
push(fakeInstance.callIds, original.callIds[i]);
});
proxyCallUtil.createCallProperties(fakeInstance);
return fakeInstance;
},
// Override proxy default implementation
matchingFakes: function (args, strict) {
return filter.call(this.fakes, function (fakeInstance) {
return matches(fakeInstance, args, strict);
});
},
};
const 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.`,
);
},
);
function createSpy(func) {
let name;
let funk = func;
if (typeof funk !== "function") {
funk = function () {
return;
};
} else {
name = functionName(funk);
}
const proxy$1 = proxy(funk, funk);
// Inherit spy API:
extend.nonEnum(proxy$1, spyApi);
extend.nonEnum(proxy$1, {
displayName: name || "spy",
fakes: [],
instantiateFake: createSpy,
id: `spy#${uuid++}`,
});
return proxy$1;
}
/**
* Creates a spy.
*
* @param {object|SinonFunction} [object] The object or function to spy on
* @param {string} [property] The property name to spy on
* @param {Array} [types] Types of accessor to spy on (get, set)
* @returns {SinonFunction|object} The spy or an object with spied accessors
*/
function spy(object, property, types) {
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]));
}
const descriptor = {};
const methodDesc = getPropertyDescriptor(object, property);
forEach(types, function (type) {
descriptor[type] = createSpy(methodDesc[type]);
});
return wrapMethod(object, property, descriptor);
}
extend(spy, spyApi);
module.exports = spy;