| 'use strict'; |
| |
| var commons = require('@sinonjs/commons'); |
| var samsam = require('@sinonjs/samsam'); |
| var collectOwnMethods = require('./collect-own-methods.js'); |
| var getPropertyDescriptor = require('./util/core/get-property-descriptor.js'); |
| var assert = require('./assert.js'); |
| var fakeTimers = require('./util/fake-timers.js'); |
| var mock = require('./mock.js'); |
| var spy = require('./spy.js'); |
| var stub = require('./stub.js'); |
| var createStubInstance = require('./create-stub-instance.js'); |
| var fake = require('./fake.js'); |
| var extend = require('./util/core/extend.js'); |
| |
| function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; } |
| |
| var commons__default = /*#__PURE__*/_interopDefault(commons); |
| var samsam__default = /*#__PURE__*/_interopDefault(samsam); |
| |
| const { array: arrayProto } = commons__default.default.prototypes; |
| const { deprecated: logger, valueToString } = commons__default.default; |
| const { createMatcher: match } = samsam__default.default; |
| |
| const DEFAULT_LEAK_THRESHOLD = 10000; |
| |
| const filter = arrayProto.filter; |
| |
| /** |
| * @callback RestorerFunction |
| * @returns {void} |
| */ |
| 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) { |
| const original = object[property]; |
| const originalType = typeof original; |
| const replacementType = typeof replacement; |
| |
| if (originalType !== replacementType) { |
| throw new TypeError( |
| `Cannot replace ${originalType} with ${replacementType}`, |
| ); |
| } |
| } |
| |
| 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"); |
| } |
| } |
| |
| /** |
| * Creates a sandbox. |
| * |
| * @param {object} [opts] Options for the sandbox |
| * @returns {object} The sandbox object |
| * @class |
| */ |
| function Sandbox(opts = {}) { |
| const sandbox = this; |
| const assertOptions = opts.assertOptions || {}; |
| const fakeRestorers = []; |
| |
| let collection = []; |
| let loggedLeakWarning = false; |
| sandbox.leakThreshold = DEFAULT_LEAK_THRESHOLD; |
| |
| function addToCollection(object) { |
| if ( |
| push(collection, object) > sandbox.leakThreshold && |
| !loggedLeakWarning |
| ) { |
| logger.printWarning( |
| `Sinon sandbox: The number of fakes in the sandbox has exceeded the leak threshold of ${sandbox.leakThreshold}. ` + |
| "To avoid memory leaks, ensure you are restoring the sandbox after each test. " + |
| "To disable this warning, modify the leakThreshold property of your sandbox.", |
| ); |
| loggedLeakWarning = true; |
| } |
| } |
| |
| sandbox.assert = assert.createAssertObject(assertOptions); |
| |
| // this is for testing only |
| sandbox.getFakes = function getFakes() { |
| return collection; |
| }; |
| |
| sandbox.createStubInstance = function createStubInstance$1() { |
| const stubbed = createStubInstance.apply(null, arguments); |
| const ownMethods = collectOwnMethods(stubbed); |
| |
| forEach(ownMethods, function (method) { |
| addToCollection(method); |
| }); |
| |
| return stubbed; |
| }; |
| |
| sandbox.inject = function inject(obj) { |
| obj.spy = function spy() { |
| return sandbox.spy.apply(null, arguments); |
| }; |
| |
| obj.stub = function stub() { |
| return sandbox.stub.apply(null, arguments); |
| }; |
| |
| obj.mock = function mock() { |
| return sandbox.mock.apply(null, arguments); |
| }; |
| |
| obj.createStubInstance = function createStubInstanceWrapper() { |
| return sandbox.createStubInstance.apply(sandbox, arguments); |
| }; |
| |
| obj.fake = function fake() { |
| return sandbox.fake.apply(null, arguments); |
| }; |
| |
| obj.define = function define() { |
| return sandbox.define.apply(null, arguments); |
| }; |
| |
| obj.replace = function replace() { |
| return sandbox.replace.apply(null, arguments); |
| }; |
| |
| obj.replaceSetter = function replaceSetter() { |
| return sandbox.replaceSetter.apply(null, arguments); |
| }; |
| |
| obj.replaceGetter = function replaceGetter() { |
| return sandbox.replaceGetter.apply(null, arguments); |
| }; |
| |
| if (sandbox.clock) { |
| obj.clock = sandbox.clock; |
| } |
| |
| obj.match = match; |
| |
| return obj; |
| }; |
| |
| function commonPostInitSetup( |
| args, |
| spy, |
| isStub, |
| shouldAddToCollection = true, |
| ) { |
| if (isStub && args.length >= 3) { |
| throw new TypeError( |
| "stub(obj, 'meth', fn) has been removed, see documentation", |
| ); |
| } |
| |
| if (shouldAddToCollection) { |
| addToCollection(spy); |
| } |
| |
| return spy; |
| } |
| |
| function addReturnedMethodsToCollection(result) { |
| if ( |
| result && |
| (typeof result === "object" || typeof result === "function") |
| ) { |
| forEach(collectOwnMethods(result), addToCollection); |
| } |
| } |
| |
| sandbox.spy = function () { |
| const createdSpy = spy.apply(spy, arguments); |
| const result = commonPostInitSetup( |
| arguments, |
| createdSpy, |
| false, |
| !(arguments.length === 1 && typeof arguments[0] === "object"), |
| ); |
| addReturnedMethodsToCollection(result); |
| return result; |
| }; |
| Object.defineProperty(sandbox.spy, "name", { |
| value: "spy", |
| configurable: true, |
| }); |
| Object.defineProperty(sandbox.spy, "length", { |
| value: 0, |
| configurable: true, |
| }); |
| extend(sandbox.spy, spy); |
| |
| sandbox.stub = function () { |
| const createdStub = stub.apply(stub, arguments); |
| const result = commonPostInitSetup( |
| arguments, |
| createdStub, |
| true, |
| !(arguments.length === 1 && typeof arguments[0] === "object"), |
| ); |
| addReturnedMethodsToCollection(result); |
| return result; |
| }; |
| Object.defineProperty(sandbox.stub, "name", { |
| value: "stub", |
| configurable: true, |
| }); |
| Object.defineProperty(sandbox.stub, "length", { |
| value: 0, |
| configurable: true, |
| }); |
| extend(sandbox.stub, stub); |
| |
| sandbox.mock = function () { |
| const m = mock.apply(null, arguments); |
| |
| addToCollection(m); |
| |
| return m; |
| }; |
| Object.defineProperty(sandbox.mock, "name", { |
| value: "mock", |
| configurable: true, |
| }); |
| Object.defineProperty(sandbox.mock, "length", { |
| value: 0, |
| configurable: true, |
| }); |
| extend(sandbox.mock, mock); |
| |
| sandbox.reset = function reset() { |
| applyOnEach(collection, "reset"); |
| applyOnEach(collection, "resetHistory"); |
| }; |
| |
| sandbox.resetBehavior = function resetBehavior() { |
| applyOnEach(collection, "resetBehavior"); |
| }; |
| |
| sandbox.resetHistory = function resetHistory() { |
| for (let i = 0; i < collection.length; i++) { |
| const f = collection[i]; |
| const method = f.resetHistory || f.reset; |
| if (typeof method === "function") { |
| method.call(f); |
| } |
| } |
| }; |
| |
| 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.restore = function restore() { |
| if (arguments.length) { |
| throw new Error( |
| "sandbox.restore() does not take any parameters. Perhaps you meant stub.restore()", |
| ); |
| } |
| |
| reverse(fakeRestorers); |
| forEach(fakeRestorers, function (restorer) { |
| restorer(); |
| }); |
| fakeRestorers.length = 0; |
| |
| reverse(collection); |
| applyOnEach(collection, "restore"); |
| collection = []; |
| }; |
| |
| sandbox.restoreContext = function restoreContext() { |
| forEach(sandbox.injectedKeys, function (injectedKey) { |
| delete sandbox.injectInto[injectedKey]; |
| }); |
| sandbox.injectedKeys.length = 0; |
| }; |
| |
| /** |
| * Creates a restorer function for the property |
| * @param {object} object the object containing the property |
| * @param {string} property the name of the property |
| * @param {boolean} [forceAssignment] if true, uses assignment instead of DefineProperty |
| * @returns {RestorerFunction} 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.sinon = true; |
| 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 ${valueToString( |
| property, |
| )} which is already replaced`, |
| ); |
| } |
| }); |
| } |
| |
| sandbox.replace = function replace(object, property, replacement) { |
| const descriptor = getPropertyDescriptor(object, property); |
| |
| checkForValidArguments(descriptor, property, replacement); |
| verifyNotReplaced(object, property); |
| throwOnAccessors(descriptor); |
| |
| verifySameType(object, property, replacement); |
| |
| // 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); |
| verifyNotReplaced(object, property); |
| |
| verifySameType(object, property, replacement); |
| |
| // 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 (typeof property === "undefined") { |
| throw new TypeError( |
| `Cannot define the already existing property ${valueToString( |
| property, |
| )}. Perhaps you meant sandbox.replace()?`, |
| ); |
| } |
| |
| if (descriptor && descriptor.isOwn) { |
| throw new TypeError( |
| `Cannot define the already existing property ${valueToString( |
| property, |
| )}. Perhaps you meant sandbox.replace()?`, |
| ); |
| } |
| |
| // store a function for restoring the defined property |
| push(fakeRestorers, getFakeRestorer(object, property)); |
| |
| Object.defineProperty(object, property, { |
| value: value, |
| configurable: true, |
| enumerable: true, |
| writable: true, |
| }); |
| |
| return value; |
| }; |
| |
| 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", |
| ); |
| } |
| |
| verifyNotReplaced(object, property); |
| |
| if (typeof descriptor.set !== "function") { |
| throw new Error("`object.property` is not a setter"); |
| } |
| |
| if (!descriptor.configurable) { |
| throw new TypeError( |
| `Descriptor for property ${valueToString( |
| property, |
| )} is not configurable`, |
| ); |
| } |
| |
| // 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: true, |
| enumerable: descriptor.enumerable, |
| }); |
| |
| return replacement; |
| }; |
| |
| 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", |
| ); |
| } |
| |
| verifyNotReplaced(object, property); |
| |
| if (typeof descriptor.get !== "function") { |
| throw new Error("`object.property` is not a getter"); |
| } |
| |
| if (!descriptor.configurable) { |
| throw new TypeError( |
| `Descriptor for property ${valueToString( |
| property, |
| )} is not configurable`, |
| ); |
| } |
| |
| // store a function for property for restoring the replaced property |
| push(fakeRestorers, getFakeRestorer(object, property)); |
| |
| Object.defineProperty(object, property, { |
| get: replacement, |
| configurable: true, |
| enumerable: descriptor.enumerable, |
| }); |
| |
| return replacement; |
| }; |
| |
| sandbox.useFakeTimers = function useFakeTimers(args) { |
| const clock = fakeTimers.useFakeTimers.call(null, args); |
| |
| sandbox.clock = clock; |
| addToCollection(clock); |
| |
| return clock; |
| }; |
| |
| sandbox.fake = function fake$1() { |
| const createdFake = fake.apply(fake, arguments); |
| const result = commonPostInitSetup(arguments, createdFake, false); |
| addToCollection(result); |
| return result; |
| }; |
| Object.defineProperty(sandbox.fake, "name", { |
| value: "fake", |
| configurable: true, |
| }); |
| Object.defineProperty(sandbox.fake, "length", { |
| value: 1, |
| configurable: true, |
| }); |
| extend(sandbox.fake, fake); |
| Object.defineProperty(sandbox.fake, "name", { |
| value: "fake", |
| configurable: true, |
| }); |
| Object.defineProperty(sandbox.fake, "length", { |
| value: 1, |
| configurable: true, |
| }); |
| |
| function addFakeBehaviorToCollection(method) { |
| const original = sandbox.fake[method]; |
| |
| sandbox.fake[method] = function () { |
| const result = original.apply(fake, arguments); |
| addToCollection(result); |
| return result; |
| }; |
| } |
| |
| addFakeBehaviorToCollection("returns"); |
| addFakeBehaviorToCollection("throws"); |
| addFakeBehaviorToCollection("resolves"); |
| addFakeBehaviorToCollection("rejects"); |
| addFakeBehaviorToCollection("yields"); |
| addFakeBehaviorToCollection("yieldsAsync"); |
| } |
| |
| Sandbox.prototype.match = match; |
| |
| module.exports = Sandbox; |