From e634e3a01deadaaa712f9c32fda9f6de585de4e5 Mon Sep 17 00:00:00 2001 From: Bartek Fabiszewski Date: Sun, 1 Dec 2019 16:04:42 +0100 Subject: [PATCH] Allow silent changes to observed property --- js/src/observe.js | 31 +++++++++++++++++++++ js/test/observe.test.js | 60 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+) diff --git a/js/src/observe.js b/js/src/observe.js index c6f14cc..ff2634f 100644 --- a/js/src/observe.js +++ b/js/src/observe.js @@ -30,6 +30,9 @@ export default class uObserve { * @param {ObserveCallback=} p2 */ static observe(obj, p1, p2) { + if (typeof obj !== 'object' || obj === null) { + throw new Error('Invalid argument: invalid object'); + } if (typeof p2 === 'function') { this.observeProperty(obj, p1, p2); } else if (typeof p1 === 'function') { @@ -62,6 +65,31 @@ export default class uObserve { } } + static isObserved(obj, property) { + if (typeof obj !== 'object' || obj === null) { + return false; + } + return obj.hasOwnProperty(property) && obj.hasOwnProperty('_values') && + obj._values.hasOwnProperty(property) && !!Object.getOwnPropertyDescriptor(obj, property)['set']; + } + + /** + * Set observed property value without notifying observers + * @param {Object} obj + * @param {string} property + * @param {*} value + */ + static setSilently(obj, property, value) { + if (!obj.hasOwnProperty(property)) { + throw new Error(`Invalid argument: object does not have property "${property}"`); + } + if (this.isObserved(obj, property)) { + obj._values[property] = value; + } else { + obj[property] = value; + } + } + /** * Observe object's property. On change call observer * @param {Object} obj @@ -69,6 +97,9 @@ export default class uObserve { * @param {ObserveCallback} observer */ static observeProperty(obj, property, observer) { + if (!obj.hasOwnProperty(property)) { + throw new Error(`Invalid argument: object does not have property "${property}"`); + } this.addObserver(obj, observer, property); if (!obj.hasOwnProperty('_values')) { Object.defineProperty(obj, '_values', { enumerable: false, configurable: false, value: {} }); diff --git a/js/test/observe.test.js b/js/test/observe.test.js index 2d77913..5223e7c 100644 --- a/js/test/observe.test.js +++ b/js/test/observe.test.js @@ -334,6 +334,65 @@ describe('Observe tests', () => { expect(resultValue2).toEqual(undefined);// eslint-disable-line no-undefined expect(array).toEqual([ 1, 2, 3 ]); }); + + it('should throw error when observing non-existing property', () => { + // given + const nonExisting = '___non-existing___'; + + expect(object.hasOwnProperty(nonExisting)).toBe(false); + // then + expect(() => uObserve.observe(object, nonExisting, (value) => { + result = true; + resultValue = value; + })).toThrow(); + + expect(object.hasOwnProperty(nonExisting)).toBe(false); + }); + + it('should throw error when observing non-object', () => { + // given + const nonExisting = '___non-existing___'; + // then + expect(() => uObserve.observe(nonExisting, (value) => { + result = true; + resultValue = value; + })).toThrow(); + }); + + it('should throw error when observing null object', () => { + // given + const nullObject = null; + // then + expect(() => uObserve.observe(nullObject, (value) => { + result = true; + resultValue = value; + })).toThrow(); + }); + + it('should not notify observers when observed property is silently changed', () => { + // given + uObserve.observe(object, 'observed', (value) => { + result = true; + resultValue = value; + }); + // when + expect(result).toBe(false); + uObserve.setSilently(object, 'observed', 2); + // then + expect(result).toBe(false); + // eslint-disable-next-line no-undefined + expect(resultValue).toBe(undefined); + }); + + it('should return true if property is observed', () => { + // when + uObserve.observe(object, 'observed', (value) => { + result = true; + resultValue = value; + }); + // then + expect(uObserve.isObserved(object, 'observed')).toBe(true); + }); }); describe('when notify is called directly', () => { @@ -351,5 +410,6 @@ describe('Observe tests', () => { expect(result).toBe(true); expect(result2).toBe(true); }); + }); });