diff --git a/js/src/observe.js b/js/src/observe.js index 65a8415..25cdea2 100644 --- a/js/src/observe.js +++ b/js/src/observe.js @@ -81,6 +81,11 @@ export default class uObserve { } if (this.isObserved(obj, property)) { obj._values[property] = value; + if (Array.isArray(obj[property])) { + for (const obs of obj._observers[property]) { + this.observeArray(obj[property], obs); + } + } } else { obj[property] = value; } @@ -110,7 +115,7 @@ export default class uObserve { uObserve.notify(obj._observers[property], newValue); } if (Array.isArray(obj[property])) { - this.observeArray(obj[property], observer); + this.observeArray(obj[property], obj._observers[property]); } } }); @@ -139,21 +144,17 @@ export default class uObserve { /** * Observe array * @param {Object} arr - * @param {ObserveCallback} observer + * @param {(ObserveCallback|Set)} observer */ static observeArray(arr, observer) { - this.addObserver(arr, observer); - [ 'pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift' ].forEach( - (operation) => { - const descriptor = Object.getOwnPropertyDescriptor(Array.prototype, operation); - descriptor.value = function () { - const result = Array.prototype[operation].apply(arr, arguments); - console.log(`[${operation}] ` + (arr.length ? `[${arr[0]}, …](${arr.length})` : arr)); - uObserve.notify(arr._observers, arr); - return result; - }; - Object.defineProperty(arr, operation, descriptor); - }); + if (observer instanceof Set) { + for (const obs of observer) { + this.addObserver(arr, obs); + } + } else { + this.addObserver(arr, observer); + } + this.overrideArrayPrototypes(arr, arguments); } /** @@ -271,11 +272,32 @@ export default class uObserve { } } - static restoreArrayPrototypes(arr) { + /** + * @param {Object} arr + */ + static overrideArrayPrototypes(arr) { [ 'pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift' ].forEach( (operation) => { const descriptor = Object.getOwnPropertyDescriptor(Array.prototype, operation); - Object.defineProperty(arr, operation, descriptor); + if (!arr.hasOwnProperty(operation)) { + descriptor.value = function () { + const result = Array.prototype[operation].apply(arr, arguments); + console.log(`[${operation}] ` + (arr.length ? `[${arr[0]}, …](${arr.length})` : arr)); + uObserve.notify(arr._observers, arr); + return result; + }; + Object.defineProperty(arr, operation, descriptor); + } + }); + } + + /** + * @param {Object} arr + */ + static restoreArrayPrototypes(arr) { + [ 'pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift' ].forEach( + (operation) => { + delete arr[operation]; }); } diff --git a/js/test/observe.test.js b/js/test/observe.test.js index 37be38c..97ed7c4 100644 --- a/js/test/observe.test.js +++ b/js/test/observe.test.js @@ -157,6 +157,8 @@ describe('Observe tests', () => { it('should retain observers after array is reassigned', () => { // given + let result2 = false; + let resultValue2; const array = [ 1, 2 ]; const newArray = [ 3, 4 ]; object = { array: array }; @@ -164,15 +166,56 @@ describe('Observe tests', () => { result = true; resultValue = value; }); + uObserve.observe(object, 'array', (value) => { + result2 = true; + resultValue2 = value; + }); // when object.array = newArray; result = false; + result2 = false; expect(result).toBe(false); + expect(result2).toBe(false); object.array.push(5); // then expect(result).toBe(true); + expect(result2).toBe(true); expect(resultValue).toEqual(newArray); + // noinspection JSUnusedAssignment + expect(resultValue2).toEqual(newArray); + }); + + it('should retain observers after array property is silently set', () => { + // given + let result2 = false; + let resultValue2; + const array = [ 1, 2 ]; + const newArray = [ 3, 4 ]; + object = { array: [] }; + uObserve.observe(object, 'array', (value) => { + result = true; + resultValue = value; + }); + uObserve.observe(object, 'array', (value) => { + result2 = true; + resultValue2 = value; + }); + // when + uObserve.setSilently(object, 'array', array); + object.array = newArray; + result = false; + result2 = false; + + expect(result).toBe(false); + expect(result2).toBe(false); + object.array.push(5); + // then + expect(result).toBe(true); + expect(result2).toBe(true); + expect(resultValue).toEqual(newArray); + // noinspection JSUnusedAssignment + expect(resultValue2).toEqual(newArray); }); });