Bug 1142515: add utils to compare simple objects to support Loop context in rooms. r=Standard8
authorMike de Boer <mdeboer@mozilla.com>
Thu, 07 May 2015 11:38:54 +0200
changeset 242661 991020c5923b65230677917124cd827ead25cb68
parent 242660 8fe39788519c4aa9a9b93797a4b270001e48d333
child 242662 de2e38a20ccaedac1944a2269f53b3fd11aa312d
push id12781
push usermdeboer@mozilla.com
push dateThu, 07 May 2015 09:42:46 +0000
treeherderfx-team@855bbeb7d41f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersStandard8
bugs1142515
milestone40.0a1
Bug 1142515: add utils to compare simple objects to support Loop context in rooms. r=Standard8
browser/components/loop/content/shared/js/utils.js
browser/components/loop/test/shared/utils_test.js
--- a/browser/components/loop/content/shared/js/utils.js
+++ b/browser/components/loop/content/shared/js/utils.js
@@ -548,16 +548,81 @@ var inChrome = typeof Components != "und
           // One byte.
           part
       );
     }
 
     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,
     REST_ERRNOS: REST_ERRNOS,
     WEBSOCKET_REASONS: WEBSOCKET_REASONS,
     STREAM_PROPERTIES: STREAM_PROPERTIES,
     SCREEN_SHARE_STATES: SCREEN_SHARE_STATES,
     ROOM_INFO_FAILURES: ROOM_INFO_FAILURES,
@@ -571,11 +636,13 @@ var inChrome = typeof Components != "und
     isFirefox: isFirefox,
     isFirefoxOS: isFirefoxOS,
     isOpera: isOpera,
     getUnsupportedPlatform: getUnsupportedPlatform,
     locationData: locationData,
     atob: atob,
     btoa: btoa,
     strToUint8Array: strToUint8Array,
-    Uint8ArrayToStr: Uint8ArrayToStr
+    Uint8ArrayToStr: Uint8ArrayToStr,
+    objectDiff: objectDiff,
+    stripFalsyValues: stripFalsyValues
   };
 }).call(inChrome ? this : loop.shared);
--- a/browser/components/loop/test/shared/utils_test.js
+++ b/browser/components/loop/test/shared/utils_test.js
@@ -313,9 +313,107 @@ describe("loop.shared.utils", function()
     it("should fetch the correct version info for Linux", function() {
       var UA = "Mozilla/5.0 (X11; Linux i686 on x86_64; rv:10.0) Gecko/20100101 Firefox/10.0";
       var result = sharedUtils.getOSVersion(UA);
 
       // Linux version can't be determined correctly.
       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 });
+    });
+  });
 });