From 7acc550f676295cebacb5e6779f313fc278052ca Mon Sep 17 00:00:00 2001 From: Mike de Boer Date: Thu, 7 May 2015 11:38:54 +0200 Subject: [PATCH] Bug 1142515: add utils to compare simple objects to support Loop context in rooms. r=Standard8 --- .../loop/content/shared/js/utils.js | 69 ++++++++++++- .../components/loop/test/shared/utils_test.js | 98 +++++++++++++++++++ 2 files changed, 166 insertions(+), 1 deletion(-) diff --git a/browser/components/loop/content/shared/js/utils.js b/browser/components/loop/content/shared/js/utils.js index b108354bd3e7..3e2e18c36db6 100644 --- a/browser/components/loop/content/shared/js/utils.js +++ b/browser/components/loop/content/shared/js/utils.js @@ -553,6 +553,71 @@ var inChrome = typeof Components != "undefined" && "utils" in Components; return result; } + /** + * Get the difference after comparing two different objects. It compares property + * names and their respective values if necessary. + * This function does _not_ recurse into object values to keep this functions' + * complexity predictable to O(2). + * + * @param {Object} a Object number 1, the comparator. + * @param {Object} b Object number 2, the comparison. + * @return {Object} The diff output, which is itself an object structured as: + * { + * updated: [prop1, prop6], + * added: [prop2], + * removed: [prop3] + * } + */ + function objectDiff(a, b) { + var propsA = a ? Object.getOwnPropertyNames(a) : []; + var propsB = b ? Object.getOwnPropertyNames(b) : []; + var diff = { + updated: [], + added: [], + removed: [] + }; + + var prop; + for (var i = 0, lA = propsA.length; i < lA; ++i) { + prop = propsA[i]; + if (propsB.indexOf(prop) == -1) { + diff.removed.push(prop); + } else if (a[prop] !== b[prop]) { + diff.updated.push(prop); + } + } + + for (var j = 0, lB = propsB.length; j < lB; ++j) { + prop = propsB[j]; + if (propsA.indexOf(prop) == -1) { + diff.added.push(prop); + } + } + + return diff; + } + + /** + * When comparing two object, you sometimes want to ignore falsy values when + * they're not persisted on the server, for example. + * This function removes all the empty/ falsy properties from the target object. + * + * @param {Object} obj Target object to strip the falsy properties from + * @return {Object} + */ + function stripFalsyValues(obj) { + var props = Object.getOwnPropertyNames(obj); + var prop; + for (var i = props.length; i >= 0; --i) { + prop = props[i]; + // If the value of the object property evaluates to |false|, delete it. + if (!obj[prop]) { + delete obj[prop]; + } + } + return obj; + } + this.utils = { CALL_TYPES: CALL_TYPES, FAILURE_DETAILS: FAILURE_DETAILS, @@ -576,6 +641,8 @@ var inChrome = typeof Components != "undefined" && "utils" in Components; atob: atob, btoa: btoa, strToUint8Array: strToUint8Array, - Uint8ArrayToStr: Uint8ArrayToStr + Uint8ArrayToStr: Uint8ArrayToStr, + objectDiff: objectDiff, + stripFalsyValues: stripFalsyValues }; }).call(inChrome ? this : loop.shared); diff --git a/browser/components/loop/test/shared/utils_test.js b/browser/components/loop/test/shared/utils_test.js index b40abccd1f1c..466878e54c4a 100644 --- a/browser/components/loop/test/shared/utils_test.js +++ b/browser/components/loop/test/shared/utils_test.js @@ -318,4 +318,102 @@ describe("loop.shared.utils", function() { expect(result).eql({ major: Infinity, minor: 0 }); }); }); + + describe("#objectDiff", function() { + var a, b, diff; + + afterEach(function() { + a = b = diff = null; + }); + + it("should find object property additions", function() { + a = { + prop1: null + }; + b = { + prop1: null, + prop2: null + }; + + diff = sharedUtils.objectDiff(a, b); + expect(diff.updated).to.eql([]); + expect(diff.removed).to.eql([]); + expect(diff.added).to.eql(["prop2"]); + }); + + it("should find object property value changes", function() { + a = { + prop1: null + }; + b = { + prop1: "null" + }; + + diff = sharedUtils.objectDiff(a, b); + expect(diff.updated).to.eql(["prop1"]); + expect(diff.removed).to.eql([]); + expect(diff.added).to.eql([]); + }); + + it("should find object property removals", function() { + a = { + prop1: null + }; + b = {}; + + diff = sharedUtils.objectDiff(a, b); + expect(diff.updated).to.eql([]); + expect(diff.removed).to.eql(["prop1"]); + expect(diff.added).to.eql([]); + }); + + it("should report a mix of removed, added and updated properties", function() { + a = { + prop1: null, + prop2: null + }; + b = { + prop1: "null", + prop3: null + }; + + diff = sharedUtils.objectDiff(a, b); + expect(diff.updated).to.eql(["prop1"]); + expect(diff.removed).to.eql(["prop2"]); + expect(diff.added).to.eql(["prop3"]); + }); + }); + + describe("#stripFalsyValues", function() { + var obj; + + afterEach(function() { + obj = null; + }); + + it("should strip falsy object property values", function() { + obj = { + prop1: null, + prop2: false, + prop3: undefined, + prop4: void 0, + prop5: "", + prop6: 0 + }; + + sharedUtils.stripFalsyValues(obj); + expect(obj).to.eql({}); + }); + + it("should keep non-falsy values", function() { + obj = { + prop1: "null", + prop2: null, + prop3: true + }; + + sharedUtils.stripFalsyValues(obj); + expect(obj).to.eql({ prop1: "null", prop3: true }); + }); + }); });