diff --git a/packages/ember-metal/lib/watch_key.js b/packages/ember-metal/lib/watch_key.js index fab66c659ad..139f12224cd 100644 --- a/packages/ember-metal/lib/watch_key.js +++ b/packages/ember-metal/lib/watch_key.js @@ -7,6 +7,8 @@ import { DEFAULT_GETTER_FUNCTION } from 'ember-metal/properties'; +let handleMandatorySetter, lookupDescriptor; + export function watchKey(obj, keyName, meta) { // can't watch length on Array - it is special... if (keyName === 'length' && Array.isArray(obj)) { return; } @@ -35,8 +37,23 @@ export function watchKey(obj, keyName, meta) { if (isEnabled('mandatory-setter')) { - var handleMandatorySetter = function handleMandatorySetter(m, obj, keyName) { - var descriptor = Object.getOwnPropertyDescriptor && Object.getOwnPropertyDescriptor(obj, keyName); + lookupDescriptor = function lookupDescriptor(obj, keyName) { + let current = obj; + while (current) { + let descriptor = Object.getOwnPropertyDescriptor(current, keyName); + + if (descriptor) { + return descriptor; + } + + current = Object.getPrototypeOf(current); + } + + return null; + }; + + handleMandatorySetter = function handleMandatorySetter(m, obj, keyName) { + let descriptor = lookupDescriptor(obj, keyName); var configurable = descriptor ? descriptor.configurable : true; var isWritable = descriptor ? descriptor.writable : true; var hasValue = descriptor ? 'value' in descriptor : true; diff --git a/packages/ember-metal/tests/accessors/mandatory_setters_test.js b/packages/ember-metal/tests/accessors/mandatory_setters_test.js index 825db59ded9..74acdbf8aca 100644 --- a/packages/ember-metal/tests/accessors/mandatory_setters_test.js +++ b/packages/ember-metal/tests/accessors/mandatory_setters_test.js @@ -56,11 +56,96 @@ if (isEnabled('mandatory-setter')) { var obj = { someProp: null }; Object.defineProperty(obj, 'someProp', { + get() { + return null; + }, + + set(value) { + equal(value, 'foo-bar', 'custom setter was called'); + } + }); + + watch(obj, 'someProp'); + ok(!hasMandatorySetter(obj, 'someProp'), 'mandatory-setter should not be installed'); + + obj.someProp = 'foo-bar'; + }); + + QUnit.test('should not setup mandatory-setter if setter is already setup on property in parent prototype', function() { + expect(2); + + function Foo() { } + + Object.defineProperty(Foo.prototype, 'someProp', { + get() { + return null; + }, + + set(value) { + equal(value, 'foo-bar', 'custom setter was called'); + } + }); + + var obj = new Foo(); + + watch(obj, 'someProp'); + ok(!hasMandatorySetter(obj, 'someProp'), 'mandatory-setter should not be installed'); + + obj.someProp = 'foo-bar'; + }); + + QUnit.test('should not setup mandatory-setter if setter is already setup on property in grandparent prototype', function() { + expect(2); + + function Foo() { } + + Object.defineProperty(Foo.prototype, 'someProp', { + get() { + return null; + }, + + set(value) { + equal(value, 'foo-bar', 'custom setter was called'); + } + }); + + function Bar() { } + Bar.prototype = Object.create(Foo.prototype); + Bar.prototype.constructor = Bar; + + var obj = new Bar(); + + watch(obj, 'someProp'); + ok(!hasMandatorySetter(obj, 'someProp'), 'mandatory-setter should not be installed'); + + obj.someProp = 'foo-bar'; + }); + + QUnit.test('should not setup mandatory-setter if setter is already setup on property in great grandparent prototype', function() { + expect(2); + + function Foo() { } + + Object.defineProperty(Foo.prototype, 'someProp', { + get() { + return null; + }, + set(value) { equal(value, 'foo-bar', 'custom setter was called'); } }); + function Bar() { } + Bar.prototype = Object.create(Foo.prototype); + Bar.prototype.constructor = Bar; + + function Qux() { } + Qux.prototype = Object.create(Bar.prototype); + Qux.prototype.constructor = Qux; + + var obj = new Qux(); + watch(obj, 'someProp'); ok(!hasMandatorySetter(obj, 'someProp'), 'mandatory-setter should not be installed');