From 08e953614408f9470ea36f16294914b6ea2af404 Mon Sep 17 00:00:00 2001 From: Saurav Azad <3856934+sauravazad@users.noreply.github.com> Date: Sat, 17 Jul 2021 20:10:45 +0530 Subject: [PATCH] Copy over acessor properties to target object #2387 --- lib/sinon/util/core/extend.js | 27 ++++++++++++++---- test/extend-test.js | 52 +++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 5 deletions(-) diff --git a/lib/sinon/util/core/extend.js b/lib/sinon/util/core/extend.js index d6b0f730d..d7fadbad9 100644 --- a/lib/sinon/util/core/extend.js +++ b/lib/sinon/util/core/extend.js @@ -102,13 +102,30 @@ module.exports = function extend(target, ...sources) { if (prop === "name" && !destOwnPropertyDescriptor.writable) { return; } - - Object.defineProperty(dest, prop, { + const descriptors = { configurable: sourceOwnPropertyDescriptor.configurable, enumerable: sourceOwnPropertyDescriptor.enumerable, - writable: sourceOwnPropertyDescriptor.writable, - value: sourceOwnPropertyDescriptor.value, - }); + }; + /* + if the sorce has an Accessor property copy over the accessor functions (get and set) + data properties has writable attribute where as acessor 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); } ); }; diff --git a/test/extend-test.js b/test/extend-test.js index 17beb43e1..0400cb0f5 100644 --- a/test/extend-test.js +++ b/test/extend-test.js @@ -78,6 +78,58 @@ describe("extend", function () { assert.equals(result, expected); }); + it("copies acessor properties into the target", function () { + var target = { + hello: "hello", + }; + const obj = { + private: 1, + }; + Object.defineProperty(obj, "lexical", { + configurable: true, + enumerable: true, + get: () => this.private, + set: (value) => { + this.private = value; + }, + }); + Object.defineProperty(obj, "instance", { + configurable: true, + enumerable: true, + get: () => obj.private, + set: (value) => { + obj.private = value; + }, + }); + Object.defineProperty(obj, "bound", { + configurable: true, + enumerable: true, + get: function () { + return this.private; + }, + set: function (value) { + this.private = value; + }, + }); + extend(target, obj); + assert.equals(target.hello, "hello"); + assert.equals(target.lexical === undefined, true); + assert.equals(target.instance, 1); + assert.equals(target.bound, 1); + target.lexical = 2; + assert.equals(target.lexical, 2); + assert.equals(target.instance, 1); + assert.equals(target.bound, 1); + target.instance = 3; + assert.equals(target.lexical, 2); + assert.equals(target.instance, 3); + assert.equals(target.bound, 1); + target.bound = 4; + assert.equals(target.lexical, 2); + assert.equals(target.instance, 3); + assert.equals(target.bound, 4); + }); + context("when 'name' property is not writable", function () { it("does not attempt to write to the property", function () { var object1 = { prop1: null };