initial commit of actions

This commit is contained in:
Dominik Polakovics Polakovics 2026-01-31 18:56:04 +01:00
commit 949ece5785
44660 changed files with 12034344 additions and 0 deletions

View file

@ -0,0 +1,56 @@
"use strict";
const behavior = require("./sinon/behavior");
const createSandbox = require("./sinon/create-sandbox");
const extend = require("./sinon/util/core/extend");
const fakeTimers = require("./sinon/util/fake-timers");
const Sandbox = require("./sinon/sandbox");
const stub = require("./sinon/stub");
const promise = require("./sinon/promise");
const nise = require("nise");
const assert = require("assert");
/**
* @param {object} opts injection point to override the default XHR lib in testing
* @param {object} opts.sinonXhrLib
* @returns {object} a configured sandbox
*/
module.exports = function createApi(opts = { sinonXhrLib: nise }) {
assert(opts?.sinonXhrLib, "No XHR lib passed in");
const { sinonXhrLib } = opts;
const apiMethods = {
createSandbox: createSandbox,
match: require("@sinonjs/samsam").createMatcher,
restoreObject: require("./sinon/restore-object"),
expectation: require("./sinon/mock-expectation"),
// fake timers
timers: fakeTimers.timers,
// fake XHR
xhr: sinonXhrLib.fakeXhr.xhr,
FakeXMLHttpRequest: sinonXhrLib.fakeXhr.FakeXMLHttpRequest,
// fake server
fakeServer: sinonXhrLib.fakeServer,
fakeServerWithClock: sinonXhrLib.fakeServerWithClock,
createFakeServer: sinonXhrLib.fakeServer.create.bind(
sinonXhrLib.fakeServer,
),
createFakeServerWithClock: sinonXhrLib.fakeServerWithClock.create.bind(
sinonXhrLib.fakeServerWithClock,
),
addBehavior: function (name, fn) {
behavior.addBehavior(stub, name, fn);
},
// fake promise
promise: promise,
};
const sandbox = new Sandbox();
return extend(sandbox, apiMethods);
};

View file

@ -0,0 +1,3 @@
{
"type": "commonjs"
}

View file

@ -0,0 +1,3 @@
"use strict";
// eslint-disable-next-line no-undef
sinon = require("./sinon");

View file

@ -0,0 +1,5 @@
"use strict";
const createApi = require("./create-sinon-api");
module.exports = createApi();

View file

@ -0,0 +1,336 @@
"use strict";
/** @module */
const arrayProto = require("@sinonjs/commons").prototypes.array;
const calledInOrder = require("@sinonjs/commons").calledInOrder;
const createMatcher = require("@sinonjs/samsam").createMatcher;
const orderByFirstCall = require("@sinonjs/commons").orderByFirstCall;
const timesInWords = require("./util/core/times-in-words");
const inspect = require("util").inspect;
const stringSlice = require("@sinonjs/commons").prototypes.string.slice;
const globalObject = require("@sinonjs/commons").global;
const arraySlice = arrayProto.slice;
const concat = arrayProto.concat;
const forEach = arrayProto.forEach;
const join = arrayProto.join;
const splice = arrayProto.splice;
function applyDefaults(obj, defaults) {
for (const key of Object.keys(defaults)) {
const val = obj[key];
if (val === null || typeof val === "undefined") {
obj[key] = defaults[key];
}
}
}
/**
* @typedef {object} CreateAssertOptions
* @global
*
* @property {boolean} [shouldLimitAssertionLogs] default is false
* @property {number} [assertionLogLimit] default is 10K
*/
/**
* Create an assertion object that exposes several methods to invoke
*
* @param {CreateAssertOptions} [opts] options bag
* @returns {object} object with multiple assertion methods
*/
function createAssertObject(opts) {
const cleanedAssertOptions = opts || {};
applyDefaults(cleanedAssertOptions, {
shouldLimitAssertionLogs: false,
assertionLogLimit: 1e4,
});
const assert = {
failException: "AssertError",
fail: function fail(message) {
let msg = message;
if (cleanedAssertOptions.shouldLimitAssertionLogs) {
msg = message.substring(
0,
cleanedAssertOptions.assertionLogLimit,
);
}
const error = new Error(msg);
error.name = this.failException || assert.failException;
throw error;
},
pass: function pass() {
return;
},
callOrder: function assertCallOrder() {
verifyIsStub.apply(null, arguments);
let expected = "";
let actual = "";
if (!calledInOrder(arguments)) {
try {
expected = join(arguments, ", ");
const calls = arraySlice(arguments);
let i = calls.length;
while (i) {
if (!calls[--i].called) {
splice(calls, i, 1);
}
}
actual = join(orderByFirstCall(calls), ", ");
} catch (e) {
// If this fails, we'll just fall back to the blank string
}
failAssertion(
this,
`expected ${expected} to be called in order but were called as ${actual}`,
);
} else {
assert.pass("callOrder");
}
},
callCount: function assertCallCount(method, count) {
verifyIsStub(method);
let msg;
if (typeof count !== "number") {
msg =
`expected ${inspect(count)} to be a number ` +
`but was of type ${typeof count}`;
failAssertion(this, msg);
} else if (method.callCount !== count) {
msg =
`expected %n to be called ${timesInWords(count)} ` +
`but was called %c%C`;
failAssertion(this, method.printf(msg));
} else {
assert.pass("callCount");
}
},
expose: function expose(target, options) {
if (!target) {
throw new TypeError("target is null or undefined");
}
const o = options || {};
const prefix =
(typeof o.prefix === "undefined" && "assert") || o.prefix;
const includeFail =
typeof o.includeFail === "undefined" || Boolean(o.includeFail);
const instance = this;
forEach(Object.keys(instance), function (method) {
if (
method !== "expose" &&
(includeFail || !/^(fail)/.test(method))
) {
target[exposedName(prefix, method)] = instance[method];
}
});
return target;
},
match: function match(actual, expectation) {
const matcher = createMatcher(expectation);
if (matcher.test(actual)) {
assert.pass("match");
} else {
const formatted = [
"expected value to match",
` expected = ${inspect(expectation)}`,
` actual = ${inspect(actual)}`,
];
failAssertion(this, join(formatted, "\n"));
}
},
};
function verifyIsStub() {
const args = arraySlice(arguments);
forEach(args, function (method) {
if (!method) {
assert.fail("fake is not a spy");
}
if (method.proxy && method.proxy.isSinonProxy) {
verifyIsStub(method.proxy);
} else {
if (typeof method !== "function") {
assert.fail(`${method} is not a function`);
}
if (typeof method.getCall !== "function") {
assert.fail(`${method} is not stubbed`);
}
}
});
}
function verifyIsValidAssertion(assertionMethod, assertionArgs) {
switch (assertionMethod) {
case "notCalled":
case "called":
case "calledOnce":
case "calledTwice":
case "calledThrice":
if (assertionArgs.length !== 0) {
assert.fail(
`${assertionMethod} takes 1 argument but was called with ${
assertionArgs.length + 1
} arguments`,
);
}
break;
default:
break;
}
}
function failAssertion(object, msg) {
const obj = object || globalObject;
const failMethod = obj.fail || assert.fail;
failMethod.call(obj, msg);
}
function mirrorPropAsAssertion(name, method, message) {
let msg = message;
let meth = method;
if (arguments.length === 2) {
msg = method;
meth = name;
}
assert[name] = function (fake) {
verifyIsStub(fake);
const args = arraySlice(arguments, 1);
let failed = false;
verifyIsValidAssertion(name, args);
if (typeof meth === "function") {
failed = !meth(fake);
} else {
failed =
typeof fake[meth] === "function"
? !fake[meth].apply(fake, args)
: !fake[meth];
}
if (failed) {
failAssertion(
this,
(fake.printf || fake.proxy.printf).apply(
fake,
concat([msg], args),
),
);
} else {
assert.pass(name);
}
};
}
function exposedName(prefix, prop) {
return !prefix || /^fail/.test(prop)
? prop
: prefix +
stringSlice(prop, 0, 1).toUpperCase() +
stringSlice(prop, 1);
}
mirrorPropAsAssertion(
"called",
"expected %n to have been called at least once but was never called",
);
mirrorPropAsAssertion(
"notCalled",
function (spy) {
return !spy.called;
},
"expected %n to not have been called but was called %c%C",
);
mirrorPropAsAssertion(
"calledOnce",
"expected %n to be called once but was called %c%C",
);
mirrorPropAsAssertion(
"calledTwice",
"expected %n to be called twice but was called %c%C",
);
mirrorPropAsAssertion(
"calledThrice",
"expected %n to be called thrice but was called %c%C",
);
mirrorPropAsAssertion(
"calledOn",
"expected %n to be called with %1 as this but was called with %t",
);
mirrorPropAsAssertion(
"alwaysCalledOn",
"expected %n to always be called with %1 as this but was called with %t",
);
mirrorPropAsAssertion("calledWithNew", "expected %n to be called with new");
mirrorPropAsAssertion(
"alwaysCalledWithNew",
"expected %n to always be called with new",
);
mirrorPropAsAssertion(
"calledWith",
"expected %n to be called with arguments %D",
);
mirrorPropAsAssertion(
"calledWithMatch",
"expected %n to be called with match %D",
);
mirrorPropAsAssertion(
"alwaysCalledWith",
"expected %n to always be called with arguments %D",
);
mirrorPropAsAssertion(
"alwaysCalledWithMatch",
"expected %n to always be called with match %D",
);
mirrorPropAsAssertion(
"calledWithExactly",
"expected %n to be called with exact arguments %D",
);
mirrorPropAsAssertion(
"calledOnceWithExactly",
"expected %n to be called once and with exact arguments %D",
);
mirrorPropAsAssertion(
"calledOnceWithMatch",
"expected %n to be called once and with match %D",
);
mirrorPropAsAssertion(
"alwaysCalledWithExactly",
"expected %n to always be called with exact arguments %D",
);
mirrorPropAsAssertion(
"neverCalledWith",
"expected %n to never be called with arguments %*%C",
);
mirrorPropAsAssertion(
"neverCalledWithMatch",
"expected %n to never be called with match %*%C",
);
mirrorPropAsAssertion("threw", "%n did not throw exception%C");
mirrorPropAsAssertion("alwaysThrew", "%n did not always throw exception%C");
return assert;
}
module.exports = createAssertObject();
module.exports.createAssertObject = createAssertObject;

View file

@ -0,0 +1,272 @@
"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);

View file

@ -0,0 +1,27 @@
"use strict";
const walk = require("./util/core/walk");
const getPropertyDescriptor = require("./util/core/get-property-descriptor");
const hasOwnProperty =
require("@sinonjs/commons").prototypes.object.hasOwnProperty;
const push = require("@sinonjs/commons").prototypes.array.push;
function collectMethod(methods, object, prop, propOwner) {
if (
typeof getPropertyDescriptor(propOwner, prop).value === "function" &&
hasOwnProperty(object, prop)
) {
push(methods, object[prop]);
}
}
// This function returns an array of all the own methods on the passed object
function collectOwnMethods(object) {
const methods = [];
walk(object, collectMethod.bind(null, methods, object));
return methods;
}
module.exports = collectOwnMethods;

View file

@ -0,0 +1,41 @@
"use strict";
module.exports = class Colorizer {
constructor(supportsColor = require("supports-color")) {
this.supportsColor = supportsColor;
}
/**
* Should be renamed to true #privateField
* when we can ensure ES2022 support
*
* @private
*/
colorize(str, color) {
if (this.supportsColor.stdout === false) {
return str;
}
return `\x1b[${color}m${str}\x1b[0m`;
}
red(str) {
return this.colorize(str, 31);
}
green(str) {
return this.colorize(str, 32);
}
cyan(str) {
return this.colorize(str, 96);
}
white(str) {
return this.colorize(str, 39);
}
bold(str) {
return this.colorize(str, 1);
}
};

View file

@ -0,0 +1,105 @@
"use strict";
const arrayProto = require("@sinonjs/commons").prototypes.array;
const Sandbox = require("./sandbox");
const forEach = arrayProto.forEach;
const push = arrayProto.push;
function prepareSandboxFromConfig(config) {
const sandbox = new Sandbox({ assertOptions: config.assertOptions });
if (config.useFakeServer) {
if (typeof config.useFakeServer === "object") {
sandbox.serverPrototype = config.useFakeServer;
}
sandbox.useFakeServer();
}
if (config.useFakeTimers) {
if (typeof config.useFakeTimers === "object") {
sandbox.useFakeTimers(config.useFakeTimers);
} else {
sandbox.useFakeTimers();
}
}
return sandbox;
}
function exposeValue(sandbox, config, key, value) {
if (!value) {
return;
}
if (config.injectInto && !(key in config.injectInto)) {
config.injectInto[key] = value;
push(sandbox.injectedKeys, key);
} else {
push(sandbox.args, value);
}
}
/**
* Options to customize a sandbox
*
* The sandbox's methods can be injected into another object for
* convenience. The `injectInto` configuration option can name an
* object to add properties to.
*
* @typedef {object} SandboxConfig
* @property {string[]} properties The properties of the API to expose on the sandbox. Examples: ['spy', 'fake', 'restore']
* @property {object} injectInto an object in which to inject properties from the sandbox (a facade). This is mostly an integration feature (sinon-test being one).
* @property {boolean} useFakeTimers whether timers are faked by default
* @property {boolean|object} useFakeServer whether XHR's are faked and the server feature enabled by default. It could also be a different default fake server implementation to use
* @property {object} [assertOptions] see CreateAssertOptions in ./assert
*
* This type def is really suffering from JSDoc not having standardized
* how to reference types defined in other modules :(
*/
/**
* A configured sinon sandbox (private type)
*
* @typedef {object} ConfiguredSinonSandboxType
* @private
* @augments Sandbox
* @property {string[]} injectedKeys the keys that have been injected (from config.injectInto)
* @property {*[]} args the arguments for the sandbox
*/
/**
* Create a sandbox
*
* As of Sinon 5 the `sinon` instance itself is a Sandbox, so you
* hardly ever need to create additional instances for the sake of testing
*
* @param config {SandboxConfig}
* @returns {Sandbox}
*/
function createSandbox(config) {
if (!config) {
return new Sandbox();
}
const configuredSandbox = prepareSandboxFromConfig(config);
configuredSandbox.args = configuredSandbox.args || [];
configuredSandbox.injectedKeys = [];
configuredSandbox.injectInto = config.injectInto;
const exposed = configuredSandbox.inject({});
if (config.properties) {
forEach(config.properties, function (prop) {
const value =
exposed[prop] || (prop === "sandbox" && configuredSandbox);
exposeValue(configuredSandbox, config, prop, value);
});
} else {
exposeValue(configuredSandbox, config, "sandbox");
}
return configuredSandbox;
}
module.exports = createSandbox;

View file

@ -0,0 +1,36 @@
"use strict";
const stub = require("./stub");
const sinonType = require("./util/core/sinon-type");
const forEach = require("@sinonjs/commons").prototypes.array.forEach;
function isStub(value) {
return sinonType.get(value) === "stub";
}
module.exports = function createStubInstance(constructor, overrides) {
if (typeof constructor !== "function") {
throw new TypeError("The constructor should be a function.");
}
const stubInstance = Object.create(constructor.prototype);
sinonType.set(stubInstance, "stub-instance");
const stubbedObject = stub(stubInstance);
forEach(Object.keys(overrides || {}), function (propertyName) {
if (propertyName in stubbedObject) {
const value = overrides[propertyName];
if (isStub(value)) {
stubbedObject[propertyName] = value;
} else {
stubbedObject[propertyName].returns(value);
}
} else {
throw new Error(
`Cannot stub ${propertyName}. Property does not exist!`,
);
}
});
return stubbedObject;
};

View file

@ -0,0 +1,305 @@
"use strict";
const arrayProto = require("@sinonjs/commons").prototypes.array;
const isPropertyConfigurable = require("./util/core/is-property-configurable");
const exportAsyncBehaviors = require("./util/core/export-async-behaviors");
const extend = require("./util/core/extend");
const slice = arrayProto.slice;
const useLeftMostCallback = -1;
const useRightMostCallback = -2;
function throwsException(fake, error, message) {
if (typeof error === "function") {
fake.exceptionCreator = error;
} else if (typeof error === "string") {
fake.exceptionCreator = function () {
const newException = new Error(
message || `Sinon-provided ${error}`,
);
newException.name = error;
return newException;
};
} else if (!error) {
fake.exceptionCreator = function () {
return new Error("Error");
};
} else {
fake.exception = error;
}
}
const defaultBehaviors = {
callsFake: function callsFake(fake, fn) {
fake.fakeFn = fn;
fake.exception = undefined;
fake.exceptionCreator = undefined;
fake.callsThrough = false;
},
callsArg: function callsArg(fake, index) {
if (typeof index !== "number") {
throw new TypeError("argument index is not number");
}
fake.callArgAt = index;
fake.callbackArguments = [];
fake.callbackContext = undefined;
fake.callArgProp = undefined;
fake.callbackAsync = false;
fake.callsThrough = false;
},
callsArgOn: function callsArgOn(fake, index, context) {
if (typeof index !== "number") {
throw new TypeError("argument index is not number");
}
fake.callArgAt = index;
fake.callbackArguments = [];
fake.callbackContext = context;
fake.callArgProp = undefined;
fake.callbackAsync = false;
fake.callsThrough = false;
},
callsArgWith: function callsArgWith(fake, index) {
if (typeof index !== "number") {
throw new TypeError("argument index is not number");
}
fake.callArgAt = index;
fake.callbackArguments = slice(arguments, 2);
fake.callbackContext = undefined;
fake.callArgProp = undefined;
fake.callbackAsync = false;
fake.callsThrough = false;
},
callsArgOnWith: function callsArgWith(fake, index, context) {
if (typeof index !== "number") {
throw new TypeError("argument index is not number");
}
fake.callArgAt = index;
fake.callbackArguments = slice(arguments, 3);
fake.callbackContext = context;
fake.callArgProp = undefined;
fake.callbackAsync = false;
fake.callsThrough = false;
},
usingPromise: function usingPromise(fake, promiseLibrary) {
fake.promiseLibrary = promiseLibrary;
},
yields: function (fake) {
fake.callArgAt = useLeftMostCallback;
fake.callbackArguments = slice(arguments, 1);
fake.callbackContext = undefined;
fake.callArgProp = undefined;
fake.callbackAsync = false;
fake.fakeFn = undefined;
fake.callsThrough = false;
},
yieldsRight: function (fake) {
fake.callArgAt = useRightMostCallback;
fake.callbackArguments = slice(arguments, 1);
fake.callbackContext = undefined;
fake.callArgProp = undefined;
fake.callbackAsync = false;
fake.callsThrough = false;
fake.fakeFn = undefined;
},
yieldsOn: function (fake, context) {
fake.callArgAt = useLeftMostCallback;
fake.callbackArguments = slice(arguments, 2);
fake.callbackContext = context;
fake.callArgProp = undefined;
fake.callbackAsync = false;
fake.callsThrough = false;
fake.fakeFn = undefined;
},
yieldsTo: function (fake, prop) {
fake.callArgAt = useLeftMostCallback;
fake.callbackArguments = slice(arguments, 2);
fake.callbackContext = undefined;
fake.callArgProp = prop;
fake.callbackAsync = false;
fake.callsThrough = false;
fake.fakeFn = undefined;
},
yieldsToOn: function (fake, prop, context) {
fake.callArgAt = useLeftMostCallback;
fake.callbackArguments = slice(arguments, 3);
fake.callbackContext = context;
fake.callArgProp = prop;
fake.callbackAsync = false;
fake.fakeFn = undefined;
},
throws: throwsException,
throwsException: throwsException,
returns: function returns(fake, value) {
fake.callsThrough = false;
fake.returnValue = value;
fake.resolve = false;
fake.reject = false;
fake.returnValueDefined = true;
fake.exception = undefined;
fake.exceptionCreator = undefined;
fake.fakeFn = undefined;
},
returnsArg: function returnsArg(fake, index) {
if (typeof index !== "number") {
throw new TypeError("argument index is not number");
}
fake.callsThrough = false;
fake.returnArgAt = index;
},
throwsArg: function throwsArg(fake, index) {
if (typeof index !== "number") {
throw new TypeError("argument index is not number");
}
fake.callsThrough = false;
fake.throwArgAt = index;
},
returnsThis: function returnsThis(fake) {
fake.returnThis = true;
fake.callsThrough = false;
},
resolves: function resolves(fake, value) {
fake.returnValue = value;
fake.resolve = true;
fake.resolveThis = false;
fake.reject = false;
fake.returnValueDefined = true;
fake.exception = undefined;
fake.exceptionCreator = undefined;
fake.fakeFn = undefined;
fake.callsThrough = false;
},
resolvesArg: function resolvesArg(fake, index) {
if (typeof index !== "number") {
throw new TypeError("argument index is not number");
}
fake.resolveArgAt = index;
fake.returnValue = undefined;
fake.resolve = true;
fake.resolveThis = false;
fake.reject = false;
fake.returnValueDefined = false;
fake.exception = undefined;
fake.exceptionCreator = undefined;
fake.fakeFn = undefined;
fake.callsThrough = false;
},
rejects: function rejects(fake, error, message) {
let reason;
if (typeof error === "string") {
reason = new Error(message || "");
reason.name = error;
} else if (!error) {
reason = new Error("Error");
} else {
reason = error;
}
fake.returnValue = reason;
fake.resolve = false;
fake.resolveThis = false;
fake.reject = true;
fake.returnValueDefined = true;
fake.exception = undefined;
fake.exceptionCreator = undefined;
fake.fakeFn = undefined;
fake.callsThrough = false;
return fake;
},
resolvesThis: function resolvesThis(fake) {
fake.returnValue = undefined;
fake.resolve = false;
fake.resolveThis = true;
fake.reject = false;
fake.returnValueDefined = false;
fake.exception = undefined;
fake.exceptionCreator = undefined;
fake.fakeFn = undefined;
fake.callsThrough = false;
},
callThrough: function callThrough(fake) {
fake.callsThrough = true;
},
callThroughWithNew: function callThroughWithNew(fake) {
fake.callsThroughWithNew = true;
},
get: function get(fake, getterFunction) {
const rootStub = fake.stub || fake;
Object.defineProperty(rootStub.rootObj, rootStub.propName, {
get: getterFunction,
configurable: isPropertyConfigurable(
rootStub.rootObj,
rootStub.propName,
),
});
return fake;
},
set: function set(fake, setterFunction) {
const rootStub = fake.stub || fake;
Object.defineProperty(
rootStub.rootObj,
rootStub.propName,
// eslint-disable-next-line accessor-pairs
{
set: setterFunction,
configurable: isPropertyConfigurable(
rootStub.rootObj,
rootStub.propName,
),
},
);
return fake;
},
value: function value(fake, newVal) {
const rootStub = fake.stub || fake;
Object.defineProperty(rootStub.rootObj, rootStub.propName, {
value: newVal,
enumerable: true,
writable: true,
configurable:
rootStub.shadowsPropOnPrototype ||
isPropertyConfigurable(rootStub.rootObj, rootStub.propName),
});
return fake;
},
};
const asyncBehaviors = exportAsyncBehaviors(defaultBehaviors);
module.exports = extend({}, defaultBehaviors, asyncBehaviors);

View file

@ -0,0 +1,287 @@
"use strict";
const arrayProto = require("@sinonjs/commons").prototypes.array;
const createProxy = require("./proxy");
const nextTick = require("./util/core/next-tick");
const slice = arrayProto.slice;
let promiseLib = Promise;
module.exports = fake;
/**
* Returns a `fake` that records all calls, arguments and return values.
*
* When an `f` argument is supplied, this implementation will be used.
*
* @example
* // create an empty fake
* var f1 = sinon.fake();
*
* f1();
*
* f1.calledOnce()
* // true
*
* @example
* function greet(greeting) {
* console.log(`Hello ${greeting}`);
* }
*
* // create a fake with implementation
* var f2 = sinon.fake(greet);
*
* // Hello world
* f2("world");
*
* f2.calledWith("world");
* // true
*
* @param {Function|undefined} f
* @returns {Function}
* @namespace
*/
function fake(f) {
if (arguments.length > 0 && typeof f !== "function") {
throw new TypeError("Expected f argument to be a Function");
}
return wrapFunc(f);
}
/**
* Creates a `fake` that returns the provided `value`, as well as recording all
* calls, arguments and return values.
*
* @example
* var f1 = sinon.fake.returns(42);
*
* f1();
* // 42
*
* @memberof fake
* @param {*} value
* @returns {Function}
*/
fake.returns = function returns(value) {
// eslint-disable-next-line jsdoc/require-jsdoc
function f() {
return value;
}
return wrapFunc(f);
};
/**
* Creates a `fake` that throws an Error.
* If the `value` argument does not have Error in its prototype chain, it will
* be used for creating a new error.
*
* @example
* var f1 = sinon.fake.throws("hello");
*
* f1();
* // Uncaught Error: hello
*
* @example
* var f2 = sinon.fake.throws(new TypeError("Invalid argument"));
*
* f2();
* // Uncaught TypeError: Invalid argument
*
* @memberof fake
* @param {*|Error} value
* @returns {Function}
*/
fake.throws = function throws(value) {
// eslint-disable-next-line jsdoc/require-jsdoc
function f() {
throw getError(value);
}
return wrapFunc(f);
};
/**
* Creates a `fake` that returns a promise that resolves to the passed `value`
* argument.
*
* @example
* var f1 = sinon.fake.resolves("apple pie");
*
* await f1();
* // "apple pie"
*
* @memberof fake
* @param {*} value
* @returns {Function}
*/
fake.resolves = function resolves(value) {
// eslint-disable-next-line jsdoc/require-jsdoc
function f() {
return promiseLib.resolve(value);
}
return wrapFunc(f);
};
/**
* Creates a `fake` that returns a promise that rejects to the passed `value`
* argument. When `value` does not have Error in its prototype chain, it will be
* wrapped in an Error.
*
* @example
* var f1 = sinon.fake.rejects(":(");
*
* try {
* await f1();
* } catch (error) {
* console.log(error);
* // ":("
* }
*
* @memberof fake
* @param {*} value
* @returns {Function}
*/
fake.rejects = function rejects(value) {
// eslint-disable-next-line jsdoc/require-jsdoc
function f() {
return promiseLib.reject(getError(value));
}
return wrapFunc(f);
};
/**
* Causes `fake` to use a custom Promise implementation, instead of the native
* Promise implementation.
*
* @example
* const bluebird = require("bluebird");
* sinon.fake.usingPromise(bluebird);
*
* @memberof fake
* @param {*} promiseLibrary
* @returns {Function}
*/
fake.usingPromise = function usingPromise(promiseLibrary) {
promiseLib = promiseLibrary;
return fake;
};
/**
* Returns a `fake` that calls the callback with the defined arguments.
*
* @example
* function callback() {
* console.log(arguments.join("*"));
* }
*
* const f1 = sinon.fake.yields("apple", "pie");
*
* f1(callback);
* // "apple*pie"
*
* @memberof fake
* @returns {Function}
*/
fake.yields = function yields() {
const values = slice(arguments);
// eslint-disable-next-line jsdoc/require-jsdoc
function f() {
const callback = arguments[arguments.length - 1];
if (typeof callback !== "function") {
throw new TypeError("Expected last argument to be a function");
}
callback.apply(null, values);
}
return wrapFunc(f);
};
/**
* Returns a `fake` that calls the callback **asynchronously** with the
* defined arguments.
*
* @example
* function callback() {
* console.log(arguments.join("*"));
* }
*
* const f1 = sinon.fake.yields("apple", "pie");
*
* f1(callback);
*
* setTimeout(() => {
* // "apple*pie"
* });
*
* @memberof fake
* @returns {Function}
*/
fake.yieldsAsync = function yieldsAsync() {
const values = slice(arguments);
// eslint-disable-next-line jsdoc/require-jsdoc
function f() {
const callback = arguments[arguments.length - 1];
if (typeof callback !== "function") {
throw new TypeError("Expected last argument to be a function");
}
nextTick(function () {
callback.apply(null, values);
});
}
return wrapFunc(f);
};
let uuid = 0;
/**
* Creates a proxy (sinon concept) from the passed function.
*
* @private
* @param {Function} f
* @returns {Function}
*/
function wrapFunc(f) {
const fakeInstance = function () {
let firstArg, lastArg;
if (arguments.length > 0) {
firstArg = arguments[0];
lastArg = arguments[arguments.length - 1];
}
const callback =
lastArg && typeof lastArg === "function" ? lastArg : undefined;
/* eslint-disable no-use-before-define */
proxy.firstArg = firstArg;
proxy.lastArg = lastArg;
proxy.callback = callback;
return f && f.apply(this, arguments);
};
const proxy = createProxy(fakeInstance, f || fakeInstance);
proxy.displayName = "fake";
proxy.id = `fake#${uuid++}`;
return proxy;
}
/**
* Returns an Error instance from the passed value, if the value is not
* already an Error instance.
*
* @private
* @param {*} value [description]
* @returns {Error} [description]
*/
function getError(value) {
return value instanceof Error ? value : new Error(value);
}

View file

@ -0,0 +1,321 @@
"use strict";
const arrayProto = require("@sinonjs/commons").prototypes.array;
const proxyInvoke = require("./proxy-invoke");
const proxyCallToString = require("./proxy-call").toString;
const timesInWords = require("./util/core/times-in-words");
const extend = require("./util/core/extend");
const match = require("@sinonjs/samsam").createMatcher;
const stub = require("./stub");
const assert = require("./assert");
const deepEqual = require("@sinonjs/samsam").deepEqual;
const inspect = require("util").inspect;
const valueToString = require("@sinonjs/commons").valueToString;
const every = arrayProto.every;
const forEach = arrayProto.forEach;
const push = arrayProto.push;
const slice = arrayProto.slice;
function callCountInWords(callCount) {
if (callCount === 0) {
return "never called";
}
return `called ${timesInWords(callCount)}`;
}
function expectedCallCountInWords(expectation) {
const min = expectation.minCalls;
const max = expectation.maxCalls;
if (typeof min === "number" && typeof max === "number") {
let str = timesInWords(min);
if (min !== max) {
str = `at least ${str} and at most ${timesInWords(max)}`;
}
return str;
}
if (typeof min === "number") {
return `at least ${timesInWords(min)}`;
}
return `at most ${timesInWords(max)}`;
}
function receivedMinCalls(expectation) {
const hasMinLimit = typeof expectation.minCalls === "number";
return !hasMinLimit || expectation.callCount >= expectation.minCalls;
}
function receivedMaxCalls(expectation) {
if (typeof expectation.maxCalls !== "number") {
return false;
}
return expectation.callCount === expectation.maxCalls;
}
function verifyMatcher(possibleMatcher, arg) {
const isMatcher = match.isMatcher(possibleMatcher);
return (isMatcher && possibleMatcher.test(arg)) || true;
}
const mockExpectation = {
minCalls: 1,
maxCalls: 1,
create: function create(methodName) {
const expectation = extend.nonEnum(stub(), mockExpectation);
delete expectation.create;
expectation.method = methodName;
return expectation;
},
invoke: function invoke(func, thisValue, args) {
this.verifyCallAllowed(thisValue, args);
return proxyInvoke.apply(this, arguments);
},
atLeast: function atLeast(num) {
if (typeof num !== "number") {
throw new TypeError(`'${valueToString(num)}' is not number`);
}
if (!this.limitsSet) {
this.maxCalls = null;
this.limitsSet = true;
}
this.minCalls = num;
return this;
},
atMost: function atMost(num) {
if (typeof num !== "number") {
throw new TypeError(`'${valueToString(num)}' is not number`);
}
if (!this.limitsSet) {
this.minCalls = null;
this.limitsSet = true;
}
this.maxCalls = num;
return this;
},
never: function never() {
return this.exactly(0);
},
once: function once() {
return this.exactly(1);
},
twice: function twice() {
return this.exactly(2);
},
thrice: function thrice() {
return this.exactly(3);
},
exactly: function exactly(num) {
if (typeof num !== "number") {
throw new TypeError(`'${valueToString(num)}' is not a number`);
}
this.atLeast(num);
return this.atMost(num);
},
met: function met() {
return !this.failed && receivedMinCalls(this);
},
verifyCallAllowed: function verifyCallAllowed(thisValue, args) {
const expectedArguments = this.expectedArguments;
if (receivedMaxCalls(this)) {
this.failed = true;
mockExpectation.fail(
`${this.method} already called ${timesInWords(this.maxCalls)}`,
);
}
if ("expectedThis" in this && this.expectedThis !== thisValue) {
mockExpectation.fail(
`${this.method} called with ${valueToString(
thisValue,
)} as thisValue, expected ${valueToString(this.expectedThis)}`,
);
}
if (!("expectedArguments" in this)) {
return;
}
if (!args) {
mockExpectation.fail(
`${this.method} received no arguments, expected ${inspect(
expectedArguments,
)}`,
);
}
if (args.length < expectedArguments.length) {
mockExpectation.fail(
`${this.method} received too few arguments (${inspect(
args,
)}), expected ${inspect(expectedArguments)}`,
);
}
if (
this.expectsExactArgCount &&
args.length !== expectedArguments.length
) {
mockExpectation.fail(
`${this.method} received too many arguments (${inspect(
args,
)}), expected ${inspect(expectedArguments)}`,
);
}
forEach(
expectedArguments,
function (expectedArgument, i) {
if (!verifyMatcher(expectedArgument, args[i])) {
mockExpectation.fail(
`${this.method} received wrong arguments ${inspect(
args,
)}, didn't match ${String(expectedArguments)}`,
);
}
if (!deepEqual(args[i], expectedArgument)) {
mockExpectation.fail(
`${this.method} received wrong arguments ${inspect(
args,
)}, expected ${inspect(expectedArguments)}`,
);
}
},
this,
);
},
allowsCall: function allowsCall(thisValue, args) {
const expectedArguments = this.expectedArguments;
if (this.met() && receivedMaxCalls(this)) {
return false;
}
if ("expectedThis" in this && this.expectedThis !== thisValue) {
return false;
}
if (!("expectedArguments" in this)) {
return true;
}
// eslint-disable-next-line no-underscore-dangle
const _args = args || [];
if (_args.length < expectedArguments.length) {
return false;
}
if (
this.expectsExactArgCount &&
_args.length !== expectedArguments.length
) {
return false;
}
return every(expectedArguments, function (expectedArgument, i) {
if (!verifyMatcher(expectedArgument, _args[i])) {
return false;
}
if (!deepEqual(_args[i], expectedArgument)) {
return false;
}
return true;
});
},
withArgs: function withArgs() {
this.expectedArguments = slice(arguments);
return this;
},
withExactArgs: function withExactArgs() {
this.withArgs.apply(this, arguments);
this.expectsExactArgCount = true;
return this;
},
on: function on(thisValue) {
this.expectedThis = thisValue;
return this;
},
toString: function () {
const args = slice(this.expectedArguments || []);
if (!this.expectsExactArgCount) {
push(args, "[...]");
}
const callStr = proxyCallToString.call({
proxy: this.method || "anonymous mock expectation",
args: args,
});
const message = `${callStr.replace(
", [...",
"[, ...",
)} ${expectedCallCountInWords(this)}`;
if (this.met()) {
return `Expectation met: ${message}`;
}
return `Expected ${message} (${callCountInWords(this.callCount)})`;
},
verify: function verify() {
if (!this.met()) {
mockExpectation.fail(String(this));
} else {
mockExpectation.pass(String(this));
}
return true;
},
pass: function pass(message) {
assert.pass(message);
},
fail: function fail(message) {
const exception = new Error(message);
exception.name = "ExpectationError";
throw exception;
},
};
module.exports = mockExpectation;

View file

@ -0,0 +1,214 @@
"use strict";
const arrayProto = require("@sinonjs/commons").prototypes.array;
const mockExpectation = require("./mock-expectation");
const proxyCallToString = require("./proxy-call").toString;
const extend = require("./util/core/extend");
const deepEqual = require("@sinonjs/samsam").deepEqual;
const wrapMethod = require("./util/core/wrap-method");
const usePromiseLibrary = require("./util/core/use-promise-library");
const concat = arrayProto.concat;
const filter = arrayProto.filter;
const forEach = arrayProto.forEach;
const every = arrayProto.every;
const join = arrayProto.join;
const push = arrayProto.push;
const slice = arrayProto.slice;
const unshift = arrayProto.unshift;
function mock(object) {
if (!object || typeof object === "string") {
return mockExpectation.create(object ? object : "Anonymous mock");
}
return mock.create(object);
}
function each(collection, callback) {
const col = collection || [];
forEach(col, callback);
}
function arrayEquals(arr1, arr2, compareLength) {
if (compareLength && arr1.length !== arr2.length) {
return false;
}
return every(arr1, function (element, i) {
return deepEqual(arr2[i], element);
});
}
extend(mock, {
create: function create(object) {
if (!object) {
throw new TypeError("object is null");
}
const mockObject = extend.nonEnum({}, mock, { object: object });
delete mockObject.create;
return mockObject;
},
expects: function expects(method) {
if (!method) {
throw new TypeError("method is falsy");
}
if (!this.expectations) {
this.expectations = {};
this.proxies = [];
this.failures = [];
}
if (!this.expectations[method]) {
this.expectations[method] = [];
const mockObject = this;
wrapMethod(this.object, method, function () {
return mockObject.invokeMethod(method, this, arguments);
});
push(this.proxies, method);
}
const expectation = mockExpectation.create(method);
expectation.wrappedMethod = this.object[method].wrappedMethod;
push(this.expectations[method], expectation);
usePromiseLibrary(this.promiseLibrary, expectation);
return expectation;
},
restore: function restore() {
const object = this.object;
each(this.proxies, function (proxy) {
if (typeof object[proxy].restore === "function") {
object[proxy].restore();
}
});
},
verify: function verify() {
const expectations = this.expectations || {};
const messages = this.failures ? slice(this.failures) : [];
const met = [];
each(this.proxies, function (proxy) {
each(expectations[proxy], function (expectation) {
if (!expectation.met()) {
push(messages, String(expectation));
} else {
push(met, String(expectation));
}
});
});
this.restore();
if (messages.length > 0) {
mockExpectation.fail(join(concat(messages, met), "\n"));
} else if (met.length > 0) {
mockExpectation.pass(join(concat(messages, met), "\n"));
}
return true;
},
usingPromise: function usingPromise(promiseLibrary) {
this.promiseLibrary = promiseLibrary;
return this;
},
invokeMethod: function invokeMethod(method, thisValue, args) {
/* if we cannot find any matching files we will explicitly call mockExpection#fail with error messages */
/* eslint consistent-return: "off" */
const expectations =
this.expectations && this.expectations[method]
? this.expectations[method]
: [];
const currentArgs = args || [];
let available;
const expectationsWithMatchingArgs = filter(
expectations,
function (expectation) {
const expectedArgs = expectation.expectedArguments || [];
return arrayEquals(
expectedArgs,
currentArgs,
expectation.expectsExactArgCount,
);
},
);
const expectationsToApply = filter(
expectationsWithMatchingArgs,
function (expectation) {
return (
!expectation.met() &&
expectation.allowsCall(thisValue, args)
);
},
);
if (expectationsToApply.length > 0) {
return expectationsToApply[0].apply(thisValue, args);
}
const messages = [];
let exhausted = 0;
forEach(expectationsWithMatchingArgs, function (expectation) {
if (expectation.allowsCall(thisValue, args)) {
available = available || expectation;
} else {
exhausted += 1;
}
});
if (available && exhausted === 0) {
return available.apply(thisValue, args);
}
forEach(expectations, function (expectation) {
push(messages, ` ${String(expectation)}`);
});
unshift(
messages,
`Unexpected call: ${proxyCallToString.call({
proxy: method,
args: args,
})}`,
);
const err = new Error();
if (!err.stack) {
// PhantomJS does not serialize the stack trace until the error has been thrown
try {
throw err;
} catch (e) {
/* empty */
}
}
push(
this.failures,
`Unexpected call: ${proxyCallToString.call({
proxy: method,
args: args,
stack: err.stack,
})}`,
);
mockExpectation.fail(join(messages, "\n"));
},
});
module.exports = mock;

View file

@ -0,0 +1,84 @@
"use strict";
const fake = require("./fake");
const isRestorable = require("./util/core/is-restorable");
const STATUS_PENDING = "pending";
const STATUS_RESOLVED = "resolved";
const STATUS_REJECTED = "rejected";
/**
* Returns a fake for a given function or undefined. If no function is given, a
* new fake is returned. If the given function is already a fake, it is
* returned as is. Otherwise the given function is wrapped in a new fake.
*
* @param {Function} [executor] The optional executor function.
* @returns {Function}
*/
function getFakeExecutor(executor) {
if (isRestorable(executor)) {
return executor;
}
if (executor) {
return fake(executor);
}
return fake();
}
/**
* Returns a new promise that exposes it's internal `status`, `resolvedValue`
* and `rejectedValue` and can be resolved or rejected from the outside by
* calling `resolve(value)` or `reject(reason)`.
*
* @param {Function} [executor] The optional executor function.
* @returns {Promise}
*/
function promise(executor) {
const fakeExecutor = getFakeExecutor(executor);
const sinonPromise = new Promise(fakeExecutor);
sinonPromise.status = STATUS_PENDING;
sinonPromise
.then(function (value) {
sinonPromise.status = STATUS_RESOLVED;
sinonPromise.resolvedValue = value;
})
.catch(function (reason) {
sinonPromise.status = STATUS_REJECTED;
sinonPromise.rejectedValue = reason;
});
/**
* Resolves or rejects the promise with the given status and value.
*
* @param {string} status
* @param {*} value
* @param {Function} callback
*/
function finalize(status, value, callback) {
if (sinonPromise.status !== STATUS_PENDING) {
throw new Error(`Promise already ${sinonPromise.status}`);
}
sinonPromise.status = status;
callback(value);
}
sinonPromise.resolve = function (value) {
finalize(STATUS_RESOLVED, value, fakeExecutor.firstCall.args[0]);
// Return the promise so that callers can await it:
return sinonPromise;
};
sinonPromise.reject = function (reason) {
finalize(STATUS_REJECTED, reason, fakeExecutor.firstCall.args[1]);
// Return a new promise that resolves when the sinon promise was
// rejected, so that callers can await it:
return new Promise(function (resolve) {
sinonPromise.catch(() => resolve());
});
};
return sinonPromise;
}
module.exports = promise;

View file

@ -0,0 +1,67 @@
"use strict";
const push = require("@sinonjs/commons").prototypes.array.push;
exports.incrementCallCount = function incrementCallCount(proxy) {
proxy.called = true;
proxy.callCount += 1;
proxy.notCalled = false;
proxy.calledOnce = proxy.callCount === 1;
proxy.calledTwice = proxy.callCount === 2;
proxy.calledThrice = proxy.callCount === 3;
};
exports.createCallProperties = function createCallProperties(proxy) {
proxy.firstCall = proxy.getCall(0);
proxy.secondCall = proxy.getCall(1);
proxy.thirdCall = proxy.getCall(2);
proxy.lastCall = proxy.getCall(proxy.callCount - 1);
};
exports.delegateToCalls = function delegateToCalls(
proxy,
method,
matchAny,
actual,
returnsValues,
notCalled,
totalCallCount,
) {
proxy[method] = function () {
if (!this.called) {
if (notCalled) {
return notCalled.apply(this, arguments);
}
return false;
}
if (totalCallCount !== undefined && this.callCount !== totalCallCount) {
return false;
}
let currentCall;
let matches = 0;
const returnValues = [];
for (let i = 0, l = this.callCount; i < l; i += 1) {
currentCall = this.getCall(i);
const returnValue = currentCall[actual || method].apply(
currentCall,
arguments,
);
push(returnValues, returnValue);
if (returnValue) {
matches += 1;
if (matchAny) {
return true;
}
}
}
if (returnsValues) {
return returnValues;
}
return matches === this.callCount;
};
};

View file

@ -0,0 +1,306 @@
"use strict";
const arrayProto = require("@sinonjs/commons").prototypes.array;
const match = require("@sinonjs/samsam").createMatcher;
const deepEqual = require("@sinonjs/samsam").deepEqual;
const functionName = require("@sinonjs/commons").functionName;
const inspect = require("util").inspect;
const valueToString = require("@sinonjs/commons").valueToString;
const concat = arrayProto.concat;
const filter = arrayProto.filter;
const join = arrayProto.join;
const map = arrayProto.map;
const reduce = arrayProto.reduce;
const slice = arrayProto.slice;
/**
* @param proxy
* @param text
* @param args
*/
function throwYieldError(proxy, text, args) {
let msg = functionName(proxy) + text;
if (args.length) {
msg += ` Received [${join(slice(args), ", ")}]`;
}
throw new Error(msg);
}
const callProto = {
calledOn: function calledOn(thisValue) {
if (match.isMatcher(thisValue)) {
return thisValue.test(this.thisValue);
}
return this.thisValue === thisValue;
},
calledWith: function calledWith() {
const self = this;
const calledWithArgs = slice(arguments);
if (calledWithArgs.length > self.args.length) {
return false;
}
return reduce(
calledWithArgs,
function (prev, arg, i) {
return prev && deepEqual(self.args[i], arg);
},
true,
);
},
calledWithMatch: function calledWithMatch() {
const self = this;
const calledWithMatchArgs = slice(arguments);
if (calledWithMatchArgs.length > self.args.length) {
return false;
}
return reduce(
calledWithMatchArgs,
function (prev, expectation, i) {
const actual = self.args[i];
return prev && match(expectation).test(actual);
},
true,
);
},
calledWithExactly: function calledWithExactly() {
return (
arguments.length === this.args.length &&
this.calledWith.apply(this, arguments)
);
},
notCalledWith: function notCalledWith() {
return !this.calledWith.apply(this, arguments);
},
notCalledWithMatch: function notCalledWithMatch() {
return !this.calledWithMatch.apply(this, arguments);
},
returned: function returned(value) {
return deepEqual(this.returnValue, value);
},
threw: function threw(error) {
if (typeof error === "undefined" || !this.exception) {
return Boolean(this.exception);
}
return this.exception === error || this.exception.name === error;
},
calledWithNew: function calledWithNew() {
return this.proxy.prototype && this.thisValue instanceof this.proxy;
},
calledBefore: function (other) {
return this.callId < other.callId;
},
calledAfter: function (other) {
return this.callId > other.callId;
},
calledImmediatelyBefore: function (other) {
return this.callId === other.callId - 1;
},
calledImmediatelyAfter: function (other) {
return this.callId === other.callId + 1;
},
callArg: function (pos) {
this.ensureArgIsAFunction(pos);
return this.args[pos]();
},
callArgOn: function (pos, thisValue) {
this.ensureArgIsAFunction(pos);
return this.args[pos].apply(thisValue);
},
callArgWith: function (pos) {
return this.callArgOnWith.apply(
this,
concat([pos, null], slice(arguments, 1)),
);
},
callArgOnWith: function (pos, thisValue) {
this.ensureArgIsAFunction(pos);
const args = slice(arguments, 2);
return this.args[pos].apply(thisValue, args);
},
throwArg: function (pos) {
if (pos > this.args.length) {
throw new TypeError(
`Not enough arguments: ${pos} required but only ${this.args.length} present`,
);
}
throw this.args[pos];
},
yield: function () {
return this.yieldOn.apply(this, concat([null], slice(arguments, 0)));
},
yieldOn: function (thisValue) {
const args = slice(this.args);
const yieldFn = filter(args, function (arg) {
return typeof arg === "function";
})[0];
if (!yieldFn) {
throwYieldError(
this.proxy,
" cannot yield since no callback was passed.",
args,
);
}
return yieldFn.apply(thisValue, slice(arguments, 1));
},
yieldTo: function (prop) {
return this.yieldToOn.apply(
this,
concat([prop, null], slice(arguments, 1)),
);
},
yieldToOn: function (prop, thisValue) {
const args = slice(this.args);
const yieldArg = filter(args, function (arg) {
return arg && typeof arg[prop] === "function";
})[0];
const yieldFn = yieldArg && yieldArg[prop];
if (!yieldFn) {
throwYieldError(
this.proxy,
` cannot yield to '${valueToString(
prop,
)}' since no callback was passed.`,
args,
);
}
return yieldFn.apply(thisValue, slice(arguments, 2));
},
toString: function () {
if (!this.args) {
return ":(";
}
let callStr = this.proxy ? `${String(this.proxy)}(` : "";
const formattedArgs = map(this.args, function (arg) {
return inspect(arg);
});
callStr = `${callStr + join(formattedArgs, ", ")})`;
if (typeof this.returnValue !== "undefined") {
callStr += ` => ${inspect(this.returnValue)}`;
}
if (this.exception) {
callStr += ` !${this.exception.name}`;
if (this.exception.message) {
callStr += `(${this.exception.message})`;
}
}
if (this.stack) {
// If we have a stack, add the first frame that's in end-user code
// Skip the first two frames because they will refer to Sinon code
callStr += (this.stack.split("\n")[3] || "unknown").replace(
/^\s*(?:at\s+|@)?/,
" at ",
);
}
return callStr;
},
ensureArgIsAFunction: function (pos) {
if (typeof this.args[pos] !== "function") {
throw new TypeError(
`Expected argument at position ${pos} to be a Function, but was ${typeof this
.args[pos]}`,
);
}
},
};
Object.defineProperty(callProto, "stack", {
enumerable: true,
configurable: true,
get: function () {
return (this.errorWithCallStack && this.errorWithCallStack.stack) || "";
},
});
callProto.invokeCallback = callProto.yield;
/**
* @param proxy
* @param thisValue
* @param args
* @param returnValue
* @param exception
* @param id
* @param errorWithCallStack
*
* @returns {object} proxyCall
*/
function createProxyCall(
proxy,
thisValue,
args,
returnValue,
exception,
id,
errorWithCallStack,
) {
if (typeof id !== "number") {
throw new TypeError("Call id is not a number");
}
let firstArg, lastArg;
if (args.length > 0) {
firstArg = args[0];
lastArg = args[args.length - 1];
}
const proxyCall = Object.create(callProto);
const callback =
lastArg && typeof lastArg === "function" ? lastArg : undefined;
proxyCall.proxy = proxy;
proxyCall.thisValue = thisValue;
proxyCall.args = args;
proxyCall.firstArg = firstArg;
proxyCall.lastArg = lastArg;
proxyCall.callback = callback;
proxyCall.returnValue = returnValue;
proxyCall.exception = exception;
proxyCall.callId = id;
proxyCall.errorWithCallStack = errorWithCallStack;
return proxyCall;
}
createProxyCall.toString = callProto.toString; // used by mocks
module.exports = createProxyCall;

View file

@ -0,0 +1,91 @@
"use strict";
const arrayProto = require("@sinonjs/commons").prototypes.array;
const proxyCallUtil = require("./proxy-call-util");
const push = arrayProto.push;
const forEach = arrayProto.forEach;
const concat = arrayProto.concat;
const ErrorConstructor = Error.prototype.constructor;
const bind = Function.prototype.bind;
let callId = 0;
module.exports = function invoke(func, thisValue, args) {
const matchings = this.matchingFakes(args);
const currentCallId = callId++;
let exception, returnValue;
proxyCallUtil.incrementCallCount(this);
push(this.thisValues, thisValue);
push(this.args, args);
push(this.callIds, currentCallId);
forEach(matchings, function (matching) {
proxyCallUtil.incrementCallCount(matching);
push(matching.thisValues, thisValue);
push(matching.args, args);
push(matching.callIds, currentCallId);
});
// Make call properties available from within the spied function:
proxyCallUtil.createCallProperties(this);
forEach(matchings, proxyCallUtil.createCallProperties);
try {
this.invoking = true;
const thisCall = this.getCall(this.callCount - 1);
if (thisCall.calledWithNew()) {
// Call through with `new`
returnValue = new (bind.apply(
this.func || func,
concat([thisValue], args),
))();
if (
typeof returnValue !== "object" &&
typeof returnValue !== "function"
) {
returnValue = thisValue;
}
} else {
returnValue = (this.func || func).apply(thisValue, args);
}
} catch (e) {
exception = e;
} finally {
delete this.invoking;
}
push(this.exceptions, exception);
push(this.returnValues, returnValue);
forEach(matchings, function (matching) {
push(matching.exceptions, exception);
push(matching.returnValues, returnValue);
});
const err = new ErrorConstructor();
// 1. Please do not get stack at this point. It may be so very slow, and not actually used
// 2. PhantomJS does not serialize the stack trace until the error has been thrown:
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/Stack
try {
throw err;
} catch (e) {
/* empty */
}
push(this.errorsWithCallStack, err);
forEach(matchings, function (matching) {
push(matching.errorsWithCallStack, err);
});
// Make return value and exception available in the calls:
proxyCallUtil.createCallProperties(this);
forEach(matchings, proxyCallUtil.createCallProperties);
if (exception !== undefined) {
throw exception;
}
return returnValue;
};

View file

@ -0,0 +1,369 @@
"use strict";
const arrayProto = require("@sinonjs/commons").prototypes.array;
const extend = require("./util/core/extend");
const functionToString = require("./util/core/function-to-string");
const proxyCall = require("./proxy-call");
const proxyCallUtil = require("./proxy-call-util");
const proxyInvoke = require("./proxy-invoke");
const inspect = require("util").inspect;
const push = arrayProto.push;
const forEach = arrayProto.forEach;
const slice = arrayProto.slice;
const emptyFakes = Object.freeze([]);
// Public API
const proxyApi = {
toString: functionToString,
named: function named(name) {
this.displayName = name;
const nameDescriptor = Object.getOwnPropertyDescriptor(this, "name");
if (nameDescriptor && nameDescriptor.configurable) {
// IE 11 functions don't have a name.
// Safari 9 has names that are not configurable.
nameDescriptor.value = name;
Object.defineProperty(this, "name", nameDescriptor);
}
return this;
},
invoke: proxyInvoke,
/*
* Hook for derived implementation to return fake instances matching the
* given arguments.
*/
matchingFakes: function (/*args, strict*/) {
return emptyFakes;
},
getCall: function getCall(index) {
let i = index;
if (i < 0) {
// Negative indices means counting backwards from the last call
i += this.callCount;
}
if (i < 0 || i >= this.callCount) {
return null;
}
return proxyCall(
this,
this.thisValues[i],
this.args[i],
this.returnValues[i],
this.exceptions[i],
this.callIds[i],
this.errorsWithCallStack[i],
);
},
getCalls: function () {
const calls = [];
let i;
for (i = 0; i < this.callCount; i++) {
push(calls, this.getCall(i));
}
return calls;
},
calledBefore: function calledBefore(proxy) {
if (!this.called) {
return false;
}
if (!proxy.called) {
return true;
}
return this.callIds[0] < proxy.callIds[proxy.callIds.length - 1];
},
calledAfter: function calledAfter(proxy) {
if (!this.called || !proxy.called) {
return false;
}
return this.callIds[this.callCount - 1] > proxy.callIds[0];
},
calledImmediatelyBefore: function calledImmediatelyBefore(proxy) {
if (!this.called || !proxy.called) {
return false;
}
return (
this.callIds[this.callCount - 1] ===
proxy.callIds[proxy.callCount - 1] - 1
);
},
calledImmediatelyAfter: function calledImmediatelyAfter(proxy) {
if (!this.called || !proxy.called) {
return false;
}
return (
this.callIds[this.callCount - 1] ===
proxy.callIds[proxy.callCount - 1] + 1
);
},
formatters: require("./spy-formatters"),
printf: function (format) {
const spyInstance = this;
const args = slice(arguments, 1);
let formatter;
return (format || "").replace(/%(.)/g, function (match, specifier) {
formatter = proxyApi.formatters[specifier];
if (typeof formatter === "function") {
return String(formatter(spyInstance, args));
} else if (!isNaN(parseInt(specifier, 10))) {
return inspect(args[specifier - 1]);
}
return `%${specifier}`;
});
},
resetHistory: function () {
if (this.invoking) {
const err = new Error(
"Cannot reset Sinon function while invoking it. " +
"Move the call to .resetHistory outside of the callback.",
);
err.name = "InvalidResetException";
throw err;
}
this.called = false;
this.notCalled = true;
this.calledOnce = false;
this.calledTwice = false;
this.calledThrice = false;
this.callCount = 0;
this.firstCall = null;
this.secondCall = null;
this.thirdCall = null;
this.lastCall = null;
this.args = [];
this.firstArg = null;
this.lastArg = null;
this.returnValues = [];
this.thisValues = [];
this.exceptions = [];
this.callIds = [];
this.errorsWithCallStack = [];
if (this.fakes) {
forEach(this.fakes, function (fake) {
fake.resetHistory();
});
}
return this;
},
};
const delegateToCalls = proxyCallUtil.delegateToCalls;
delegateToCalls(proxyApi, "calledOn", true);
delegateToCalls(proxyApi, "alwaysCalledOn", false, "calledOn");
delegateToCalls(proxyApi, "calledWith", true);
delegateToCalls(
proxyApi,
"calledOnceWith",
true,
"calledWith",
false,
undefined,
1,
);
delegateToCalls(proxyApi, "calledWithMatch", true);
delegateToCalls(proxyApi, "alwaysCalledWith", false, "calledWith");
delegateToCalls(proxyApi, "alwaysCalledWithMatch", false, "calledWithMatch");
delegateToCalls(proxyApi, "calledWithExactly", true);
delegateToCalls(
proxyApi,
"calledOnceWithExactly",
true,
"calledWithExactly",
false,
undefined,
1,
);
delegateToCalls(
proxyApi,
"calledOnceWithMatch",
true,
"calledWithMatch",
false,
undefined,
1,
);
delegateToCalls(
proxyApi,
"alwaysCalledWithExactly",
false,
"calledWithExactly",
);
delegateToCalls(
proxyApi,
"neverCalledWith",
false,
"notCalledWith",
false,
function () {
return true;
},
);
delegateToCalls(
proxyApi,
"neverCalledWithMatch",
false,
"notCalledWithMatch",
false,
function () {
return true;
},
);
delegateToCalls(proxyApi, "threw", true);
delegateToCalls(proxyApi, "alwaysThrew", false, "threw");
delegateToCalls(proxyApi, "returned", true);
delegateToCalls(proxyApi, "alwaysReturned", false, "returned");
delegateToCalls(proxyApi, "calledWithNew", true);
delegateToCalls(proxyApi, "alwaysCalledWithNew", false, "calledWithNew");
function createProxy(func, originalFunc) {
const proxy = wrapFunction(func, originalFunc);
// Inherit function properties:
extend(proxy, func);
proxy.prototype = func.prototype;
extend.nonEnum(proxy, proxyApi);
return proxy;
}
function wrapFunction(func, originalFunc) {
const arity = originalFunc.length;
let p;
// Do not change this to use an eval. Projects that depend on sinon block the use of eval.
// ref: https://github.com/sinonjs/sinon/issues/710
switch (arity) {
/*eslint-disable no-unused-vars, max-len*/
case 0:
p = function proxy() {
return p.invoke(func, this, slice(arguments));
};
break;
case 1:
p = function proxy(a) {
return p.invoke(func, this, slice(arguments));
};
break;
case 2:
p = function proxy(a, b) {
return p.invoke(func, this, slice(arguments));
};
break;
case 3:
p = function proxy(a, b, c) {
return p.invoke(func, this, slice(arguments));
};
break;
case 4:
p = function proxy(a, b, c, d) {
return p.invoke(func, this, slice(arguments));
};
break;
case 5:
p = function proxy(a, b, c, d, e) {
return p.invoke(func, this, slice(arguments));
};
break;
case 6:
p = function proxy(a, b, c, d, e, f) {
return p.invoke(func, this, slice(arguments));
};
break;
case 7:
p = function proxy(a, b, c, d, e, f, g) {
return p.invoke(func, this, slice(arguments));
};
break;
case 8:
p = function proxy(a, b, c, d, e, f, g, h) {
return p.invoke(func, this, slice(arguments));
};
break;
case 9:
p = function proxy(a, b, c, d, e, f, g, h, i) {
return p.invoke(func, this, slice(arguments));
};
break;
case 10:
p = function proxy(a, b, c, d, e, f, g, h, i, j) {
return p.invoke(func, this, slice(arguments));
};
break;
case 11:
p = function proxy(a, b, c, d, e, f, g, h, i, j, k) {
return p.invoke(func, this, slice(arguments));
};
break;
case 12:
p = function proxy(a, b, c, d, e, f, g, h, i, j, k, l) {
return p.invoke(func, this, slice(arguments));
};
break;
default:
p = function proxy() {
return p.invoke(func, this, slice(arguments));
};
break;
/*eslint-enable*/
}
const nameDescriptor = Object.getOwnPropertyDescriptor(
originalFunc,
"name",
);
if (nameDescriptor && nameDescriptor.configurable) {
// IE 11 functions don't have a name.
// Safari 9 has names that are not configurable.
Object.defineProperty(p, "name", nameDescriptor);
}
extend.nonEnum(p, {
isSinonProxy: true,
called: false,
notCalled: true,
calledOnce: false,
calledTwice: false,
calledThrice: false,
callCount: 0,
firstCall: null,
firstArg: null,
secondCall: null,
thirdCall: null,
lastCall: null,
lastArg: null,
args: [],
returnValues: [],
thisValues: [],
exceptions: [],
callIds: [],
errorsWithCallStack: [],
});
return p;
}
module.exports = createProxy;

View file

@ -0,0 +1,17 @@
"use strict";
const walkObject = require("./util/core/walk-object");
function filter(object, property) {
return object[property].restore && object[property].restore.sinon;
}
function restore(object, property) {
object[property].restore();
}
function restoreObject(object) {
return walkObject(restore, object, filter);
}
module.exports = restoreObject;

View file

@ -0,0 +1,538 @@
"use strict";
const arrayProto = require("@sinonjs/commons").prototypes.array;
const logger = require("@sinonjs/commons").deprecated;
const collectOwnMethods = require("./collect-own-methods");
const getPropertyDescriptor = require("./util/core/get-property-descriptor");
const isPropertyConfigurable = require("./util/core/is-property-configurable");
const match = require("@sinonjs/samsam").createMatcher;
const sinonAssert = require("./assert");
const sinonClock = require("./util/fake-timers");
const sinonMock = require("./mock");
const sinonSpy = require("./spy");
const sinonStub = require("./stub");
const sinonCreateStubInstance = require("./create-stub-instance");
const sinonFake = require("./fake");
const valueToString = require("@sinonjs/commons").valueToString;
const fakeServer = require("nise").fakeServer;
const fakeXhr = require("nise").fakeXhr;
const usePromiseLibrary = require("./util/core/use-promise-library");
const DEFAULT_LEAK_THRESHOLD = 10000;
const filter = arrayProto.filter;
const forEach = arrayProto.forEach;
const push = arrayProto.push;
const reverse = arrayProto.reverse;
function applyOnEach(fakes, method) {
const matchingFakes = filter(fakes, function (fake) {
return typeof fake[method] === "function";
});
forEach(matchingFakes, function (fake) {
fake[method]();
});
}
function throwOnAccessors(descriptor) {
if (typeof descriptor.get === "function") {
throw new Error("Use sandbox.replaceGetter for replacing getters");
}
if (typeof descriptor.set === "function") {
throw new Error("Use sandbox.replaceSetter for replacing setters");
}
}
function verifySameType(object, property, replacement) {
if (typeof object[property] !== typeof replacement) {
throw new TypeError(
`Cannot replace ${typeof object[
property
]} with ${typeof replacement}`,
);
}
}
function checkForValidArguments(descriptor, property, replacement) {
if (typeof descriptor === "undefined") {
throw new TypeError(
`Cannot replace non-existent property ${valueToString(
property,
)}. Perhaps you meant sandbox.define()?`,
);
}
if (typeof replacement === "undefined") {
throw new TypeError("Expected replacement argument to be defined");
}
}
/**
* A sinon sandbox
*
* @param opts
* @param {object} [opts.assertOptions] see the CreateAssertOptions in ./assert
* @class
*/
function Sandbox(opts = {}) {
const sandbox = this;
const assertOptions = opts.assertOptions || {};
let fakeRestorers = [];
let promiseLib;
let collection = [];
let loggedLeakWarning = false;
sandbox.leakThreshold = DEFAULT_LEAK_THRESHOLD;
function addToCollection(object) {
if (
push(collection, object) > sandbox.leakThreshold &&
!loggedLeakWarning
) {
// eslint-disable-next-line no-console
logger.printWarning(
"Potential memory leak detected; be sure to call restore() to clean up your sandbox. To suppress this warning, modify the leakThreshold property of your sandbox.",
);
loggedLeakWarning = true;
}
}
sandbox.assert = sinonAssert.createAssertObject(assertOptions);
sandbox.serverPrototype = fakeServer;
// this is for testing only
sandbox.getFakes = function getFakes() {
return collection;
};
sandbox.createStubInstance = function createStubInstance() {
const stubbed = sinonCreateStubInstance.apply(null, arguments);
const ownMethods = collectOwnMethods(stubbed);
forEach(ownMethods, function (method) {
addToCollection(method);
});
usePromiseLibrary(promiseLib, ownMethods);
return stubbed;
};
sandbox.inject = function inject(obj) {
obj.spy = function () {
return sandbox.spy.apply(null, arguments);
};
obj.stub = function () {
return sandbox.stub.apply(null, arguments);
};
obj.mock = function () {
return sandbox.mock.apply(null, arguments);
};
obj.createStubInstance = function () {
return sandbox.createStubInstance.apply(sandbox, arguments);
};
obj.fake = function () {
return sandbox.fake.apply(null, arguments);
};
obj.define = function () {
return sandbox.define.apply(null, arguments);
};
obj.replace = function () {
return sandbox.replace.apply(null, arguments);
};
obj.replaceSetter = function () {
return sandbox.replaceSetter.apply(null, arguments);
};
obj.replaceGetter = function () {
return sandbox.replaceGetter.apply(null, arguments);
};
if (sandbox.clock) {
obj.clock = sandbox.clock;
}
if (sandbox.server) {
obj.server = sandbox.server;
obj.requests = sandbox.server.requests;
}
obj.match = match;
return obj;
};
sandbox.mock = function mock() {
const m = sinonMock.apply(null, arguments);
addToCollection(m);
usePromiseLibrary(promiseLib, m);
return m;
};
sandbox.reset = function reset() {
applyOnEach(collection, "reset");
applyOnEach(collection, "resetHistory");
};
sandbox.resetBehavior = function resetBehavior() {
applyOnEach(collection, "resetBehavior");
};
sandbox.resetHistory = function resetHistory() {
function privateResetHistory(f) {
const method = f.resetHistory || f.reset;
if (method) {
method.call(f);
}
}
forEach(collection, privateResetHistory);
};
sandbox.restore = function restore() {
if (arguments.length) {
throw new Error(
"sandbox.restore() does not take any parameters. Perhaps you meant stub.restore()",
);
}
reverse(collection);
applyOnEach(collection, "restore");
collection = [];
forEach(fakeRestorers, function (restorer) {
restorer();
});
fakeRestorers = [];
sandbox.restoreContext();
};
sandbox.restoreContext = function restoreContext() {
if (!sandbox.injectedKeys) {
return;
}
forEach(sandbox.injectedKeys, function (injectedKey) {
delete sandbox.injectInto[injectedKey];
});
sandbox.injectedKeys.length = 0;
};
/**
* Creates a restorer function for the property
*
* @param {object|Function} object
* @param {string} property
* @param {boolean} forceAssignment
* @returns {Function} restorer function
*/
function getFakeRestorer(object, property, forceAssignment = false) {
const descriptor = getPropertyDescriptor(object, property);
const value = forceAssignment && object[property];
function restorer() {
if (forceAssignment) {
object[property] = value;
} else if (descriptor?.isOwn) {
Object.defineProperty(object, property, descriptor);
} else {
delete object[property];
}
}
restorer.object = object;
restorer.property = property;
return restorer;
}
function verifyNotReplaced(object, property) {
forEach(fakeRestorers, function (fakeRestorer) {
if (
fakeRestorer.object === object &&
fakeRestorer.property === property
) {
throw new TypeError(
`Attempted to replace ${property} which is already replaced`,
);
}
});
}
/**
* Replace an existing property
*
* @param {object|Function} object
* @param {string} property
* @param {*} replacement a fake, stub, spy or any other value
* @returns {*}
*/
sandbox.replace = function replace(object, property, replacement) {
const descriptor = getPropertyDescriptor(object, property);
checkForValidArguments(descriptor, property, replacement);
throwOnAccessors(descriptor);
verifySameType(object, property, replacement);
verifyNotReplaced(object, property);
// store a function for restoring the replaced property
push(fakeRestorers, getFakeRestorer(object, property));
object[property] = replacement;
return replacement;
};
sandbox.replace.usingAccessor = function replaceUsingAccessor(
object,
property,
replacement,
) {
const descriptor = getPropertyDescriptor(object, property);
checkForValidArguments(descriptor, property, replacement);
verifySameType(object, property, replacement);
verifyNotReplaced(object, property);
// store a function for restoring the replaced property
push(fakeRestorers, getFakeRestorer(object, property, true));
object[property] = replacement;
return replacement;
};
sandbox.define = function define(object, property, value) {
const descriptor = getPropertyDescriptor(object, property);
if (descriptor) {
throw new TypeError(
`Cannot define the already existing property ${valueToString(
property,
)}. Perhaps you meant sandbox.replace()?`,
);
}
if (typeof value === "undefined") {
throw new TypeError("Expected value argument to be defined");
}
verifyNotReplaced(object, property);
// store a function for restoring the defined property
push(fakeRestorers, getFakeRestorer(object, property));
object[property] = value;
return value;
};
sandbox.replaceGetter = function replaceGetter(
object,
property,
replacement,
) {
const descriptor = getPropertyDescriptor(object, property);
if (typeof descriptor === "undefined") {
throw new TypeError(
`Cannot replace non-existent property ${valueToString(
property,
)}`,
);
}
if (typeof replacement !== "function") {
throw new TypeError(
"Expected replacement argument to be a function",
);
}
if (typeof descriptor.get !== "function") {
throw new Error("`object.property` is not a getter");
}
verifyNotReplaced(object, property);
// store a function for restoring the replaced property
push(fakeRestorers, getFakeRestorer(object, property));
Object.defineProperty(object, property, {
get: replacement,
configurable: isPropertyConfigurable(object, property),
});
return replacement;
};
sandbox.replaceSetter = function replaceSetter(
object,
property,
replacement,
) {
const descriptor = getPropertyDescriptor(object, property);
if (typeof descriptor === "undefined") {
throw new TypeError(
`Cannot replace non-existent property ${valueToString(
property,
)}`,
);
}
if (typeof replacement !== "function") {
throw new TypeError(
"Expected replacement argument to be a function",
);
}
if (typeof descriptor.set !== "function") {
throw new Error("`object.property` is not a setter");
}
verifyNotReplaced(object, property);
// store a function for restoring the replaced property
push(fakeRestorers, getFakeRestorer(object, property));
// eslint-disable-next-line accessor-pairs
Object.defineProperty(object, property, {
set: replacement,
configurable: isPropertyConfigurable(object, property),
});
return replacement;
};
function commonPostInitSetup(args, spy) {
const [object, property, types] = args;
const isSpyingOnEntireObject =
typeof property === "undefined" && typeof object === "object";
if (isSpyingOnEntireObject) {
const ownMethods = collectOwnMethods(spy);
forEach(ownMethods, function (method) {
addToCollection(method);
});
usePromiseLibrary(promiseLib, ownMethods);
} else if (Array.isArray(types)) {
for (const accessorType of types) {
addToCollection(spy[accessorType]);
usePromiseLibrary(promiseLib, spy[accessorType]);
}
} else {
addToCollection(spy);
usePromiseLibrary(promiseLib, spy);
}
return spy;
}
sandbox.spy = function spy() {
const createdSpy = sinonSpy.apply(sinonSpy, arguments);
return commonPostInitSetup(arguments, createdSpy);
};
sandbox.stub = function stub() {
const createdStub = sinonStub.apply(sinonStub, arguments);
return commonPostInitSetup(arguments, createdStub);
};
// eslint-disable-next-line no-unused-vars
sandbox.fake = function fake(f) {
const s = sinonFake.apply(sinonFake, arguments);
addToCollection(s);
return s;
};
forEach(Object.keys(sinonFake), function (key) {
const fakeBehavior = sinonFake[key];
if (typeof fakeBehavior === "function") {
sandbox.fake[key] = function () {
const s = fakeBehavior.apply(fakeBehavior, arguments);
addToCollection(s);
return s;
};
}
});
sandbox.useFakeTimers = function useFakeTimers(args) {
const clock = sinonClock.useFakeTimers.call(null, args);
sandbox.clock = clock;
addToCollection(clock);
return clock;
};
sandbox.verify = function verify() {
applyOnEach(collection, "verify");
};
sandbox.verifyAndRestore = function verifyAndRestore() {
let exception;
try {
sandbox.verify();
} catch (e) {
exception = e;
}
sandbox.restore();
if (exception) {
throw exception;
}
};
sandbox.useFakeServer = function useFakeServer() {
const proto = sandbox.serverPrototype || fakeServer;
if (!proto || !proto.create) {
return null;
}
sandbox.server = proto.create();
addToCollection(sandbox.server);
return sandbox.server;
};
sandbox.useFakeXMLHttpRequest = function useFakeXMLHttpRequest() {
const xhr = fakeXhr.useFakeXMLHttpRequest();
addToCollection(xhr);
return xhr;
};
sandbox.usingPromise = function usingPromise(promiseLibrary) {
promiseLib = promiseLibrary;
collection.promiseLibrary = promiseLibrary;
return sandbox;
};
}
Sandbox.prototype.match = match;
module.exports = Sandbox;

View file

@ -0,0 +1,163 @@
"use strict";
const arrayProto = require("@sinonjs/commons").prototypes.array;
const Colorizer = require("./colorizer");
const colororizer = new Colorizer();
const match = require("@sinonjs/samsam").createMatcher;
const timesInWords = require("./util/core/times-in-words");
const inspect = require("util").inspect;
const jsDiff = require("diff");
const join = arrayProto.join;
const map = arrayProto.map;
const push = arrayProto.push;
const slice = arrayProto.slice;
/**
*
* @param matcher
* @param calledArg
* @param calledArgMessage
*
* @returns {string} the colored text
*/
function colorSinonMatchText(matcher, calledArg, calledArgMessage) {
let calledArgumentMessage = calledArgMessage;
let matcherMessage = matcher.message;
if (!matcher.test(calledArg)) {
matcherMessage = colororizer.red(matcher.message);
if (calledArgumentMessage) {
calledArgumentMessage = colororizer.green(calledArgumentMessage);
}
}
return `${calledArgumentMessage} ${matcherMessage}`;
}
/**
* @param diff
*
* @returns {string} the colored diff
*/
function colorDiffText(diff) {
const objects = map(diff, function (part) {
let text = part.value;
if (part.added) {
text = colororizer.green(text);
} else if (part.removed) {
text = colororizer.red(text);
}
if (diff.length === 2) {
text += " "; // format simple diffs
}
return text;
});
return join(objects, "");
}
/**
*
* @param value
* @returns {string} a quoted string
*/
function quoteStringValue(value) {
if (typeof value === "string") {
return JSON.stringify(value);
}
return value;
}
module.exports = {
c: function (spyInstance) {
return timesInWords(spyInstance.callCount);
},
n: function (spyInstance) {
// eslint-disable-next-line @sinonjs/no-prototype-methods/no-prototype-methods
return spyInstance.toString();
},
D: function (spyInstance, args) {
let message = "";
for (let i = 0, l = spyInstance.callCount; i < l; ++i) {
// describe multiple calls
if (l > 1) {
message += `\nCall ${i + 1}:`;
}
const calledArgs = spyInstance.getCall(i).args;
const expectedArgs = slice(args);
for (
let j = 0;
j < calledArgs.length || j < expectedArgs.length;
++j
) {
let calledArg = calledArgs[j];
let expectedArg = expectedArgs[j];
if (calledArg) {
calledArg = quoteStringValue(calledArg);
}
if (expectedArg) {
expectedArg = quoteStringValue(expectedArg);
}
message += "\n";
const calledArgMessage =
j < calledArgs.length ? inspect(calledArg) : "";
if (match.isMatcher(expectedArg)) {
message += colorSinonMatchText(
expectedArg,
calledArg,
calledArgMessage,
);
} else {
const expectedArgMessage =
j < expectedArgs.length ? inspect(expectedArg) : "";
const diff = jsDiff.diffJson(
calledArgMessage,
expectedArgMessage,
);
message += colorDiffText(diff);
}
}
}
return message;
},
C: function (spyInstance) {
const calls = [];
for (let i = 0, l = spyInstance.callCount; i < l; ++i) {
// eslint-disable-next-line @sinonjs/no-prototype-methods/no-prototype-methods
let stringifiedCall = ` ${spyInstance.getCall(i).toString()}`;
if (/\n/.test(calls[i - 1])) {
stringifiedCall = `\n${stringifiedCall}`;
}
push(calls, stringifiedCall);
}
return calls.length > 0 ? `\n${join(calls, "\n")}` : "";
},
t: function (spyInstance) {
const objects = [];
for (let i = 0, l = spyInstance.callCount; i < l; ++i) {
push(objects, inspect(spyInstance.thisValues[i]));
}
return join(objects, ", ");
},
"*": function (spyInstance, args) {
return join(
map(args, function (arg) {
return inspect(arg);
}),
", ",
);
},
};

View file

@ -0,0 +1,192 @@
"use strict";
const arrayProto = require("@sinonjs/commons").prototypes.array;
const createProxy = require("./proxy");
const extend = require("./util/core/extend");
const functionName = require("@sinonjs/commons").functionName;
const getPropertyDescriptor = require("./util/core/get-property-descriptor");
const deepEqual = require("@sinonjs/samsam").deepEqual;
const isEsModule = require("./util/core/is-es-module");
const proxyCallUtil = require("./proxy-call-util");
const walkObject = require("./util/core/walk-object");
const wrapMethod = require("./util/core/wrap-method");
const valueToString = require("@sinonjs/commons").valueToString;
/* cache references to library methods so that they also can be stubbed without problems */
const forEach = arrayProto.forEach;
const pop = arrayProto.pop;
const push = arrayProto.push;
const slice = arrayProto.slice;
const filter = Array.prototype.filter;
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 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 @sinonjs/no-prototype-methods/no-prototype-methods */
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 = 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) {
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;

View file

@ -0,0 +1,257 @@
"use strict";
const arrayProto = require("@sinonjs/commons").prototypes.array;
const behavior = require("./behavior");
const behaviors = require("./default-behaviors");
const createProxy = require("./proxy");
const functionName = require("@sinonjs/commons").functionName;
const hasOwnProperty =
require("@sinonjs/commons").prototypes.object.hasOwnProperty;
const isNonExistentProperty = require("./util/core/is-non-existent-property");
const spy = require("./spy");
const extend = require("./util/core/extend");
const getPropertyDescriptor = require("./util/core/get-property-descriptor");
const isEsModule = require("./util/core/is-es-module");
const sinonType = require("./util/core/sinon-type");
const wrapMethod = require("./util/core/wrap-method");
const throwOnFalsyObject = require("./throw-on-falsy-object");
const valueToString = require("@sinonjs/commons").valueToString;
const walkObject = require("./util/core/walk-object");
const forEach = arrayProto.forEach;
const pop = arrayProto.pop;
const slice = arrayProto.slice;
const sort = arrayProto.sort;
let uuid = 0;
function createStub(originalFunc) {
// eslint-disable-next-line prefer-const
let proxy;
function functionStub() {
const args = slice(arguments);
const matchings = proxy.matchingFakes(args);
const fnStub =
pop(
sort(matchings, function (a, b) {
return (
a.matchingArguments.length - b.matchingArguments.length
);
}),
) || proxy;
return getCurrentBehavior(fnStub).invoke(this, arguments);
}
proxy = createProxy(functionStub, originalFunc || functionStub);
// Inherit spy API:
extend.nonEnum(proxy, spy);
// Inherit stub API:
extend.nonEnum(proxy, stub);
const name = originalFunc ? functionName(originalFunc) : null;
extend.nonEnum(proxy, {
fakes: [],
instantiateFake: createStub,
displayName: name || "stub",
defaultBehavior: null,
behaviors: [],
id: `stub#${uuid++}`,
});
sinonType.set(proxy, "stub");
return proxy;
}
function stub(object, property) {
if (arguments.length > 2) {
throw new TypeError(
"stub(obj, 'meth', fn) has been removed, see documentation",
);
}
if (isEsModule(object)) {
throw new TypeError("ES Modules cannot be stubbed");
}
throwOnFalsyObject.apply(null, arguments);
if (isNonExistentProperty(object, property)) {
throw new TypeError(
`Cannot stub non-existent property ${valueToString(property)}`,
);
}
const actualDescriptor = getPropertyDescriptor(object, property);
assertValidPropertyDescriptor(actualDescriptor, property);
const isObjectOrFunction =
typeof object === "object" || typeof object === "function";
const isStubbingEntireObject =
typeof property === "undefined" && isObjectOrFunction;
const isCreatingNewStub = !object && typeof property === "undefined";
const isStubbingNonFuncProperty =
isObjectOrFunction &&
typeof property !== "undefined" &&
(typeof actualDescriptor === "undefined" ||
typeof actualDescriptor.value !== "function");
if (isStubbingEntireObject) {
return walkObject(stub, object);
}
if (isCreatingNewStub) {
return createStub();
}
const func =
typeof actualDescriptor.value === "function"
? actualDescriptor.value
: null;
const s = createStub(func);
extend.nonEnum(s, {
rootObj: object,
propName: property,
shadowsPropOnPrototype: !actualDescriptor.isOwn,
restore: function restore() {
if (actualDescriptor !== undefined && actualDescriptor.isOwn) {
Object.defineProperty(object, property, actualDescriptor);
return;
}
delete object[property];
},
});
return isStubbingNonFuncProperty ? s : wrapMethod(object, property, s);
}
function assertValidPropertyDescriptor(descriptor, property) {
if (!descriptor || !property) {
return;
}
if (descriptor.isOwn && !descriptor.configurable && !descriptor.writable) {
throw new TypeError(
`Descriptor for property ${property} is non-configurable and non-writable`,
);
}
if ((descriptor.get || descriptor.set) && !descriptor.configurable) {
throw new TypeError(
`Descriptor for accessor property ${property} is non-configurable`,
);
}
if (isDataDescriptor(descriptor) && !descriptor.writable) {
throw new TypeError(
`Descriptor for data property ${property} is non-writable`,
);
}
}
function isDataDescriptor(descriptor) {
return (
!descriptor.value &&
!descriptor.writable &&
!descriptor.set &&
!descriptor.get
);
}
/*eslint-disable no-use-before-define*/
function getParentBehaviour(stubInstance) {
return stubInstance.parent && getCurrentBehavior(stubInstance.parent);
}
function getDefaultBehavior(stubInstance) {
return (
stubInstance.defaultBehavior ||
getParentBehaviour(stubInstance) ||
behavior.create(stubInstance)
);
}
function getCurrentBehavior(stubInstance) {
const currentBehavior = stubInstance.behaviors[stubInstance.callCount - 1];
return currentBehavior && currentBehavior.isPresent()
? currentBehavior
: getDefaultBehavior(stubInstance);
}
/*eslint-enable no-use-before-define*/
const proto = {
resetBehavior: function () {
this.defaultBehavior = null;
this.behaviors = [];
delete this.returnValue;
delete this.returnArgAt;
delete this.throwArgAt;
delete this.resolveArgAt;
delete this.fakeFn;
this.returnThis = false;
this.resolveThis = false;
forEach(this.fakes, function (fake) {
fake.resetBehavior();
});
},
reset: function () {
this.resetHistory();
this.resetBehavior();
},
onCall: function onCall(index) {
if (!this.behaviors[index]) {
this.behaviors[index] = behavior.create(this);
}
return this.behaviors[index];
},
onFirstCall: function onFirstCall() {
return this.onCall(0);
},
onSecondCall: function onSecondCall() {
return this.onCall(1);
},
onThirdCall: function onThirdCall() {
return this.onCall(2);
},
withArgs: function withArgs() {
const fake = spy.withArgs.apply(this, arguments);
if (this.defaultBehavior && this.defaultBehavior.promiseLibrary) {
fake.defaultBehavior =
fake.defaultBehavior || behavior.create(fake);
fake.defaultBehavior.promiseLibrary =
this.defaultBehavior.promiseLibrary;
}
return fake;
},
};
forEach(Object.keys(behavior), function (method) {
if (
hasOwnProperty(behavior, method) &&
!hasOwnProperty(proto, method) &&
method !== "create" &&
method !== "invoke"
) {
proto[method] = behavior.createBehavior(method);
}
});
forEach(Object.keys(behaviors), function (method) {
if (hasOwnProperty(behaviors, method) && !hasOwnProperty(proto, method)) {
behavior.addBehavior(stub, method, behaviors[method]);
}
});
extend(stub, proto);
module.exports = stub;

View file

@ -0,0 +1,13 @@
"use strict";
const valueToString = require("@sinonjs/commons").valueToString;
function throwOnFalsyObject(object, property) {
if (property && !object) {
const type = object === null ? "null" : "undefined";
throw new Error(
`Trying to stub property '${valueToString(property)}' of ${type}`,
);
}
}
module.exports = throwOnFalsyObject;

View file

@ -0,0 +1,25 @@
"use strict";
const arrayProto = require("@sinonjs/commons").prototypes.array;
const reduce = arrayProto.reduce;
module.exports = function exportAsyncBehaviors(behaviorMethods) {
return reduce(
Object.keys(behaviorMethods),
function (acc, method) {
// need to avoid creating another async versions of the newly added async methods
if (method.match(/^(callsArg|yields)/) && !method.match(/Async/)) {
acc[`${method}Async`] = function () {
const result = behaviorMethods[method].apply(
this,
arguments,
);
this.callbackAsync = true;
return result;
};
}
return acc;
},
{},
);
};

View file

@ -0,0 +1,161 @@
"use strict";
const arrayProto = require("@sinonjs/commons").prototypes.array;
const hasOwnProperty =
require("@sinonjs/commons").prototypes.object.hasOwnProperty;
const join = arrayProto.join;
const push = arrayProto.push;
// Adapted from https://developer.mozilla.org/en/docs/ECMAScript_DontEnum_attribute#JScript_DontEnum_Bug
const hasDontEnumBug = (function () {
const obj = {
constructor: function () {
return "0";
},
toString: function () {
return "1";
},
valueOf: function () {
return "2";
},
toLocaleString: function () {
return "3";
},
prototype: function () {
return "4";
},
isPrototypeOf: function () {
return "5";
},
propertyIsEnumerable: function () {
return "6";
},
hasOwnProperty: function () {
return "7";
},
length: function () {
return "8";
},
unique: function () {
return "9";
},
};
const result = [];
for (const prop in obj) {
if (hasOwnProperty(obj, prop)) {
push(result, obj[prop]());
}
}
return join(result, "") !== "0123456789";
})();
/**
*
* @param target
* @param sources
* @param doCopy
* @returns {*} target
*/
function extendCommon(target, sources, doCopy) {
let source, i, prop;
for (i = 0; i < sources.length; i++) {
source = sources[i];
for (prop in source) {
if (hasOwnProperty(source, prop)) {
doCopy(target, source, prop);
}
}
// Make sure we copy (own) toString method even when in JScript with DontEnum bug
// See https://developer.mozilla.org/en/docs/ECMAScript_DontEnum_attribute#JScript_DontEnum_Bug
if (
hasDontEnumBug &&
hasOwnProperty(source, "toString") &&
source.toString !== target.toString
) {
target.toString = source.toString;
}
}
return target;
}
/**
* Public: Extend target in place with all (own) properties, except 'name' when [[writable]] is false,
* from sources in-order. Thus, last source will override properties in previous sources.
*
* @param {object} target - The Object to extend
* @param {object[]} sources - Objects to copy properties from.
* @returns {object} the extended target
*/
module.exports = function extend(target, ...sources) {
return extendCommon(
target,
sources,
function copyValue(dest, source, prop) {
const destOwnPropertyDescriptor = Object.getOwnPropertyDescriptor(
dest,
prop,
);
const sourceOwnPropertyDescriptor = Object.getOwnPropertyDescriptor(
source,
prop,
);
if (prop === "name" && !destOwnPropertyDescriptor.writable) {
return;
}
const descriptors = {
configurable: sourceOwnPropertyDescriptor.configurable,
enumerable: sourceOwnPropertyDescriptor.enumerable,
};
/*
if the source has an Accessor property copy over the accessor functions (get and set)
data properties has writable attribute where as accessor property don't
REF: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#properties
*/
if (hasOwnProperty(sourceOwnPropertyDescriptor, "writable")) {
descriptors.writable = sourceOwnPropertyDescriptor.writable;
descriptors.value = sourceOwnPropertyDescriptor.value;
} else {
if (sourceOwnPropertyDescriptor.get) {
descriptors.get =
sourceOwnPropertyDescriptor.get.bind(dest);
}
if (sourceOwnPropertyDescriptor.set) {
descriptors.set =
sourceOwnPropertyDescriptor.set.bind(dest);
}
}
Object.defineProperty(dest, prop, descriptors);
},
);
};
/**
* Public: Extend target in place with all (own) properties from sources in-order. Thus, last source will
* override properties in previous sources. Define the properties as non enumerable.
*
* @param {object} target - The Object to extend
* @param {object[]} sources - Objects to copy properties from.
* @returns {object} the extended target
*/
module.exports.nonEnum = function extendNonEnum(target, ...sources) {
return extendCommon(
target,
sources,
function copyProperty(dest, source, prop) {
Object.defineProperty(dest, prop, {
value: source[prop],
enumerable: false,
configurable: true,
writable: true,
});
},
);
};

View file

@ -0,0 +1,25 @@
"use strict";
module.exports = function toString() {
let i, prop, thisValue;
if (this.getCall && this.callCount) {
i = this.callCount;
while (i--) {
thisValue = this.getCall(i).thisValue;
// eslint-disable-next-line guard-for-in
for (prop in thisValue) {
try {
if (thisValue[prop] === this) {
return prop;
}
} catch (e) {
// no-op - accessing props can throw an error, nothing to do here
}
}
}
}
return this.displayName || "sinon fake";
};

View file

@ -0,0 +1,18 @@
"use strict";
/* istanbul ignore next : not testing that setTimeout works */
function nextTick(callback) {
setTimeout(callback, 0);
}
module.exports = function getNextTick(process, setImmediate) {
if (typeof process === "object" && typeof process.nextTick === "function") {
return process.nextTick;
}
if (typeof setImmediate === "function") {
return setImmediate;
}
return nextTick;
};

View file

@ -0,0 +1,55 @@
"use strict";
/**
* @typedef {object} PropertyDescriptor
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty#description
* @property {boolean} configurable defaults to false
* @property {boolean} enumerable defaults to false
* @property {boolean} writable defaults to false
* @property {*} value defaults to undefined
* @property {Function} get defaults to undefined
* @property {Function} set defaults to undefined
*/
/*
* The following type def is strictly speaking illegal in JSDoc, but the expression forms a
* legal Typescript union type and is understood by Visual Studio and the IntelliJ
* family of editors. The "TS" flavor of JSDoc is becoming the de-facto standard these
* days for that reason (and the fact that JSDoc is essentially unmaintained)
*/
/**
* @typedef {{isOwn: boolean} & PropertyDescriptor} SinonPropertyDescriptor
* a slightly enriched property descriptor
* @property {boolean} isOwn true if the descriptor is owned by this object, false if it comes from the prototype
*/
/**
* Returns a slightly modified property descriptor that one can tell is from the object or the prototype
*
* @param {*} object
* @param {string} property
* @returns {SinonPropertyDescriptor}
*/
function getPropertyDescriptor(object, property) {
let proto = object;
let descriptor;
const isOwn = Boolean(
object && Object.getOwnPropertyDescriptor(object, property),
);
while (
proto &&
!(descriptor = Object.getOwnPropertyDescriptor(proto, property))
) {
proto = Object.getPrototypeOf(proto);
}
if (descriptor) {
descriptor.isOwn = isOwn;
}
return descriptor;
}
module.exports = getPropertyDescriptor;

View file

@ -0,0 +1,20 @@
"use strict";
/**
* Verify if an object is a ECMAScript Module
*
* As the exports from a module is immutable we cannot alter the exports
* using spies or stubs. Let the consumer know this to avoid bug reports
* on weird error messages.
*
* @param {object} object The object to examine
* @returns {boolean} true when the object is a module
*/
module.exports = function (object) {
return (
object &&
typeof Symbol !== "undefined" &&
object[Symbol.toStringTag] === "Module" &&
Object.isSealed(object)
);
};

View file

@ -0,0 +1,14 @@
"use strict";
/**
* @param {*} object
* @param {string} property
* @returns {boolean} whether a prop exists in the prototype chain
*/
function isNonExistentProperty(object, property) {
return Boolean(
object && typeof property !== "undefined" && !(property in object),
);
}
module.exports = isNonExistentProperty;

View file

@ -0,0 +1,11 @@
"use strict";
const getPropertyDescriptor = require("./get-property-descriptor");
function isPropertyConfigurable(obj, propName) {
const propertyDescriptor = getPropertyDescriptor(obj, propName);
return propertyDescriptor ? propertyDescriptor.configurable : true;
}
module.exports = isPropertyConfigurable;

View file

@ -0,0 +1,11 @@
"use strict";
function isRestorable(obj) {
return (
typeof obj === "function" &&
typeof obj.restore === "function" &&
obj.restore.sinon
);
}
module.exports = isRestorable;

View file

@ -0,0 +1,6 @@
"use strict";
const globalObject = require("@sinonjs/commons").global;
const getNextTick = require("./get-next-tick");
module.exports = getNextTick(globalObject.process, globalObject.setImmediate);

View file

@ -0,0 +1,22 @@
"use strict";
const sinonTypeSymbolProperty = Symbol("SinonType");
module.exports = {
/**
* Set the type of a Sinon object to make it possible to identify it later at runtime
*
* @param {object|Function} object object/function to set the type on
* @param {string} type the named type of the object/function
*/
set(object, type) {
Object.defineProperty(object, sinonTypeSymbolProperty, {
value: type,
configurable: false,
enumerable: false,
});
},
get(object) {
return object && object[sinonTypeSymbolProperty];
},
};

View file

@ -0,0 +1,7 @@
"use strict";
const array = [null, "once", "twice", "thrice"];
module.exports = function timesInWords(count) {
return array[count] || `${count || 0} times`;
};

View file

@ -0,0 +1,21 @@
"use strict";
const forEach = Array.prototype.forEach;
function usePromiseLibrary(library, fakes) {
if (typeof library === "undefined") {
return;
}
if (Array.isArray(fakes)) {
forEach.call(fakes, usePromiseLibrary.bind(null, library));
return;
}
if (typeof fakes.usingPromise === "function") {
fakes.usingPromise(library);
}
}
module.exports = usePromiseLibrary;

View file

@ -0,0 +1,55 @@
"use strict";
const functionName = require("@sinonjs/commons").functionName;
const getPropertyDescriptor = require("./get-property-descriptor");
const walk = require("./walk");
/**
* A utility that allows traversing an object, applying mutating functions on the properties
*
* @param {Function} mutator called on each property
* @param {object} object the object we are walking over
* @param {Function} filter a predicate (boolean function) that will decide whether or not to apply the mutator to the current property
* @returns {void} nothing
*/
function walkObject(mutator, object, filter) {
let called = false;
const name = functionName(mutator);
if (!object) {
throw new Error(
`Trying to ${name} object but received ${String(object)}`,
);
}
walk(object, function (prop, propOwner) {
// we don't want to stub things like toString(), valueOf(), etc. so we only stub if the object
// is not Object.prototype
if (
propOwner !== Object.prototype &&
prop !== "constructor" &&
typeof getPropertyDescriptor(propOwner, prop).value === "function"
) {
if (filter) {
if (filter(object, prop)) {
called = true;
mutator(object, prop);
}
} else {
called = true;
mutator(object, prop);
}
}
});
if (!called) {
throw new Error(
`Found no methods on object to which we could apply mutations`,
);
}
return object;
}
module.exports = walkObject;

View file

@ -0,0 +1,49 @@
"use strict";
const forEach = require("@sinonjs/commons").prototypes.array.forEach;
function walkInternal(obj, iterator, context, originalObj, seen) {
let prop;
const proto = Object.getPrototypeOf(obj);
if (typeof Object.getOwnPropertyNames !== "function") {
// We explicitly want to enumerate through all of the prototype's properties
// in this case, therefore we deliberately leave out an own property check.
/* eslint-disable-next-line guard-for-in */
for (prop in obj) {
iterator.call(context, obj[prop], prop, obj);
}
return;
}
forEach(Object.getOwnPropertyNames(obj), function (k) {
if (seen[k] !== true) {
seen[k] = true;
const target =
typeof Object.getOwnPropertyDescriptor(obj, k).get ===
"function"
? originalObj
: obj;
iterator.call(context, k, target);
}
});
if (proto) {
walkInternal(proto, iterator, context, originalObj, seen);
}
}
/* Walks the prototype chain of an object and iterates over every own property
* name encountered. The iterator is called in the same fashion that Array.prototype.forEach
* works, where it is passed the value, key, and own object as the 1st, 2nd, and 3rd positional
* argument, respectively. In cases where Object.getOwnPropertyNames is not available, walk will
* default to using a simple for..in loop.
*
* obj - The object to walk the prototype chain for.
* iterator - The function to be called on each pass of the walk.
* context - (Optional) When given, the iterator will be called with this object as the receiver.
*/
module.exports = function walk(obj, iterator, context) {
return walkInternal(obj, iterator, context, obj, {});
};

View file

@ -0,0 +1,245 @@
"use strict";
// eslint-disable-next-line no-empty-function
const noop = () => {};
const getPropertyDescriptor = require("./get-property-descriptor");
const extend = require("./extend");
const sinonType = require("./sinon-type");
const hasOwnProperty =
require("@sinonjs/commons").prototypes.object.hasOwnProperty;
const valueToString = require("@sinonjs/commons").valueToString;
const push = require("@sinonjs/commons").prototypes.array.push;
function isFunction(obj) {
return (
typeof obj === "function" ||
Boolean(obj && obj.constructor && obj.call && obj.apply)
);
}
function mirrorProperties(target, source) {
for (const prop in source) {
if (!hasOwnProperty(target, prop)) {
target[prop] = source[prop];
}
}
}
function getAccessor(object, property, method) {
const accessors = ["get", "set"];
const descriptor = getPropertyDescriptor(object, property);
for (let i = 0; i < accessors.length; i++) {
if (
descriptor[accessors[i]] &&
descriptor[accessors[i]].name === method.name
) {
return accessors[i];
}
}
return null;
}
// Cheap way to detect if we have ES5 support.
const hasES5Support = "keys" in Object;
module.exports = function wrapMethod(object, property, method) {
if (!object) {
throw new TypeError("Should wrap property of object");
}
if (typeof method !== "function" && typeof method !== "object") {
throw new TypeError(
"Method wrapper should be a function or a property descriptor",
);
}
function checkWrappedMethod(wrappedMethod) {
let error;
if (!isFunction(wrappedMethod)) {
error = new TypeError(
`Attempted to wrap ${typeof wrappedMethod} property ${valueToString(
property,
)} as function`,
);
} else if (wrappedMethod.restore && wrappedMethod.restore.sinon) {
error = new TypeError(
`Attempted to wrap ${valueToString(
property,
)} which is already wrapped`,
);
} else if (wrappedMethod.calledBefore) {
const verb = wrappedMethod.returns ? "stubbed" : "spied on";
error = new TypeError(
`Attempted to wrap ${valueToString(
property,
)} which is already ${verb}`,
);
}
if (error) {
if (wrappedMethod && wrappedMethod.stackTraceError) {
error.stack += `\n--------------\n${wrappedMethod.stackTraceError.stack}`;
}
throw error;
}
}
let error, wrappedMethod, i, wrappedMethodDesc, target, accessor;
const wrappedMethods = [];
function simplePropertyAssignment() {
wrappedMethod = object[property];
checkWrappedMethod(wrappedMethod);
object[property] = method;
method.displayName = property;
}
// Firefox has a problem when using hasOwn.call on objects from other frames.
const owned = object.hasOwnProperty
? object.hasOwnProperty(property) // eslint-disable-line @sinonjs/no-prototype-methods/no-prototype-methods
: hasOwnProperty(object, property);
if (hasES5Support) {
const methodDesc =
typeof method === "function" ? { value: method } : method;
wrappedMethodDesc = getPropertyDescriptor(object, property);
if (!wrappedMethodDesc) {
error = new TypeError(
`Attempted to wrap ${typeof wrappedMethod} property ${property} as function`,
);
} else if (
wrappedMethodDesc.restore &&
wrappedMethodDesc.restore.sinon
) {
error = new TypeError(
`Attempted to wrap ${property} which is already wrapped`,
);
}
if (error) {
if (wrappedMethodDesc && wrappedMethodDesc.stackTraceError) {
error.stack += `\n--------------\n${wrappedMethodDesc.stackTraceError.stack}`;
}
throw error;
}
const types = Object.keys(methodDesc);
for (i = 0; i < types.length; i++) {
wrappedMethod = wrappedMethodDesc[types[i]];
checkWrappedMethod(wrappedMethod);
push(wrappedMethods, wrappedMethod);
}
mirrorProperties(methodDesc, wrappedMethodDesc);
for (i = 0; i < types.length; i++) {
mirrorProperties(methodDesc[types[i]], wrappedMethodDesc[types[i]]);
}
// you are not allowed to flip the configurable prop on an
// existing descriptor to anything but false (#2514)
if (!owned) {
methodDesc.configurable = true;
}
Object.defineProperty(object, property, methodDesc);
// catch failing assignment
// this is the converse of the check in `.restore` below
if (typeof method === "function" && object[property] !== method) {
// correct any wrongdoings caused by the defineProperty call above,
// such as adding new items (if object was a Storage object)
delete object[property];
simplePropertyAssignment();
}
} else {
simplePropertyAssignment();
}
extendObjectWithWrappedMethods();
function extendObjectWithWrappedMethods() {
for (i = 0; i < wrappedMethods.length; i++) {
accessor = getAccessor(object, property, wrappedMethods[i]);
target = accessor ? method[accessor] : method;
extend.nonEnum(target, {
displayName: property,
wrappedMethod: wrappedMethods[i],
// Set up an Error object for a stack trace which can be used later to find what line of
// code the original method was created on.
stackTraceError: new Error("Stack Trace for original"),
restore: restore,
});
target.restore.sinon = true;
if (!hasES5Support) {
mirrorProperties(target, wrappedMethod);
}
}
}
function restore() {
accessor = getAccessor(object, property, this.wrappedMethod);
let descriptor;
// For prototype properties try to reset by delete first.
// If this fails (ex: localStorage on mobile safari) then force a reset
// via direct assignment.
if (accessor) {
if (!owned) {
try {
// In some cases `delete` may throw an error
delete object[property][accessor];
} catch (e) {} // eslint-disable-line no-empty
// For native code functions `delete` fails without throwing an error
// on Chrome < 43, PhantomJS, etc.
} else if (hasES5Support) {
descriptor = getPropertyDescriptor(object, property);
descriptor[accessor] = wrappedMethodDesc[accessor];
Object.defineProperty(object, property, descriptor);
}
if (hasES5Support) {
descriptor = getPropertyDescriptor(object, property);
if (descriptor && descriptor.value === target) {
object[property][accessor] = this.wrappedMethod;
}
} else {
// Use strict equality comparison to check failures then force a reset
// via direct assignment.
if (object[property][accessor] === target) {
object[property][accessor] = this.wrappedMethod;
}
}
} else {
if (!owned) {
try {
delete object[property];
} catch (e) {} // eslint-disable-line no-empty
} else if (hasES5Support) {
Object.defineProperty(object, property, wrappedMethodDesc);
}
if (hasES5Support) {
descriptor = getPropertyDescriptor(object, property);
if (descriptor && descriptor.value === target) {
object[property] = this.wrappedMethod;
}
} else {
if (object[property] === target) {
object[property] = this.wrappedMethod;
}
}
}
if (sinonType.get(object) === "stub-instance") {
// this is simply to avoid errors after restoring if something should
// traverse the object in a cleanup phase, ref #2477
object[property] = noop;
}
}
return method;
};

View file

@ -0,0 +1,90 @@
"use strict";
const extend = require("./core/extend");
const FakeTimers = require("@sinonjs/fake-timers");
const globalObject = require("@sinonjs/commons").global;
/**
*
* @param config
* @param globalCtx
*
* @returns {object} the clock, after installing it on the global context, if given
*/
function createClock(config, globalCtx) {
let FakeTimersCtx = FakeTimers;
if (globalCtx !== null && typeof globalCtx === "object") {
FakeTimersCtx = FakeTimers.withGlobal(globalCtx);
}
const clock = FakeTimersCtx.install(config);
clock.restore = clock.uninstall;
return clock;
}
/**
*
* @param obj
* @param globalPropName
*/
function addIfDefined(obj, globalPropName) {
const globalProp = globalObject[globalPropName];
if (typeof globalProp !== "undefined") {
obj[globalPropName] = globalProp;
}
}
/**
* @param {number|Date|object} dateOrConfig The unix epoch value to install with (default 0)
* @returns {object} Returns a lolex clock instance
*/
exports.useFakeTimers = function (dateOrConfig) {
const hasArguments = typeof dateOrConfig !== "undefined";
const argumentIsDateLike =
(typeof dateOrConfig === "number" || dateOrConfig instanceof Date) &&
arguments.length === 1;
const argumentIsObject =
dateOrConfig !== null &&
typeof dateOrConfig === "object" &&
arguments.length === 1;
if (!hasArguments) {
return createClock({
now: 0,
});
}
if (argumentIsDateLike) {
return createClock({
now: dateOrConfig,
});
}
if (argumentIsObject) {
const config = extend.nonEnum({}, dateOrConfig);
const globalCtx = config.global;
delete config.global;
return createClock(config, globalCtx);
}
throw new TypeError(
"useFakeTimers expected epoch or config object. See https://github.com/sinonjs/sinon",
);
};
exports.clock = {
create: function (now) {
return FakeTimers.createClock(now);
},
};
const timers = {
setTimeout: setTimeout,
clearTimeout: clearTimeout,
setInterval: setInterval,
clearInterval: clearInterval,
Date: Date,
};
addIfDefined(timers, "setImmediate");
addIfDefined(timers, "clearImmediate");
exports.timers = timers;