diff --git a/docs/release-source/release/stubs.md b/docs/release-source/release/stubs.md index 7fd0d68c9..8bc556f68 100644 --- a/docs/release-source/release/stubs.md +++ b/docs/release-source/release/stubs.md @@ -492,7 +492,6 @@ You can restore values by calling the `restore` method: #### `stub.wrappedMethod` -Holds a reference to the original method/function this stub has -wrapped. `undefined` for the property accessors. +Holds a reference to the original method/function this stub has wrapped. [matchers]: ../matchers diff --git a/lib/sinon/util/core/wrap-method.js b/lib/sinon/util/core/wrap-method.js index 1fd3a3dd4..0ea80bc76 100644 --- a/lib/sinon/util/core/wrap-method.js +++ b/lib/sinon/util/core/wrap-method.js @@ -5,6 +5,7 @@ var extend = require("./extend"); var hasOwnProperty = require("@sinonjs/commons").prototypes.object .hasOwnProperty; var valueToString = require("@sinonjs/commons").valueToString; +var push = require("@sinonjs/commons").prototypes.array.push; function isFunction(obj) { return ( @@ -21,6 +22,18 @@ function mirrorProperties(target, source) { } } +function getAccessor(object, property, method) { + var accessors = ["get", "set"]; + var descriptor = getPropertyDescriptor(object, property); + + for (var 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. var hasES5Support = "keys" in Object; @@ -67,7 +80,9 @@ module.exports = function wrapMethod(object, property, method) { } } - var error, wrappedMethod, i, wrappedMethodDesc; + var error, wrappedMethods, wrappedMethod, i, wrappedMethodDesc, target, accessor; + + wrappedMethods = []; function simplePropertyAssignment() { wrappedMethod = object[property]; @@ -109,6 +124,7 @@ module.exports = function wrapMethod(object, property, method) { for (i = 0; i < types.length; i++) { wrappedMethod = wrappedMethodDesc[types[i]]; checkWrappedMethod(wrappedMethod); + push(wrappedMethods, wrappedMethod); } mirrorProperties(methodDesc, wrappedMethodDesc); @@ -129,49 +145,83 @@ module.exports = function wrapMethod(object, property, method) { simplePropertyAssignment(); } - extend.nonEnum(method, { - displayName: property, + 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], - wrappedMethod: wrappedMethod, + // 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"), - // 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 + }); - restore: function () { - // For prototype properties try to reset by delete first. - // If this fails (ex: localStorage on mobile safari) then force a reset - // via direct assignment. + target.restore.sinon = true; + if (!hasES5Support) { + mirrorProperties(target, wrappedMethod); + } + } + } + + function restore() { + accessor = getAccessor(object, property, this.wrappedMethod); + var 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) { - // In some cases `delete` may throw an error try { - delete object[property]; + // 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) { - Object.defineProperty(object, property, wrappedMethodDesc); + descriptor = getPropertyDescriptor(object, property); + descriptor[accessor] = wrappedMethodDesc[accessor]; + Object.defineProperty(object, property, descriptor); } if (hasES5Support) { - var descriptor = getPropertyDescriptor(object, property); - if (descriptor && descriptor.value === method) { - object[property] = wrappedMethod; + 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] === method) { - object[property] = wrappedMethod; + if (object[property][accessor] === target) { + object[property][accessor] = this.wrappedMethod; } } - }, - }); - method.restore.sinon = true; + } else { + if (!owned) { + try { + delete object[property]; + } catch (e) {} // eslint-disable-line no-empty + } else if (hasES5Support) { + Object.defineProperty(object, property, wrappedMethodDesc); + } - if (!hasES5Support) { - mirrorProperties(method, wrappedMethod); + if (hasES5Support) { + descriptor = getPropertyDescriptor(object, property); + if (descriptor && descriptor.value === target) { + object[property] = this.wrappedMethod; + } + } else { + if (object[property] === target) { + object[property] = this.wrappedMethod; + } + } + } } return method; diff --git a/test/spy-test.js b/test/spy-test.js index f89cdc68f..1925cad83 100644 --- a/test/spy-test.js +++ b/test/spy-test.js @@ -329,8 +329,38 @@ describe("spy", function () { assert(spy.get.calledOnce); }); - describe("global.Error", function () { - beforeEach(function () { + it("sets wrappedMethod on getter and setter", function() { + var object = { + get test() { + return this.property; + }, + set test(value) { + this.property = value; + } + }; + + var descriptor1 = Object.getOwnPropertyDescriptor(object, "test"); + var spy = createSpy(object, "test", ["get", "set"]); + var descriptor2 = Object.getOwnPropertyDescriptor(object, "test"); + + refute.equals(descriptor1, descriptor2); + + refute.isUndefined(spy.get.wrappedMethod); + refute.isUndefined(spy.get.restore); + refute.isUndefined(spy.set.wrappedMethod); + refute.isUndefined(spy.set.restore); + assert.isUndefined(spy.wrappedMethod); + assert.isUndefined(spy.restore); + + spy.get.restore(); + spy.set.restore(); + + var descriptor3 = Object.getOwnPropertyDescriptor(object, "test"); + assert.equals(descriptor1, descriptor3); + }); + + describe("global.Error", function() { + beforeEach(function() { this.originalError = globalContext.Error; });