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 242725 991020c5923b65230677917124cd827ead25cb68
parent 242724 8fe39788519c4aa9a9b93797a4b270001e48d333
child 242726 de2e38a20ccaedac1944a2269f53b3fd11aa312d
push id28707
push userkwierso@gmail.com
push dateThu, 07 May 2015 21:58:59 +0000
treeherdermozilla-central@5e83fa48971d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersStandard8
bugs1142515
milestone40.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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 });
+    });
+  });
 });