Bug 1444240 - Filter address options to eliminate dupes based on fields to display. r=MattN
authorSam Foster <sfoster@mozilla.com>
Fri, 09 Mar 2018 11:49:21 -0800
changeset 775167 c1ee3abb5d406cdc9f3726176b2f84ee1b48e3bb
parent 775166 aeeb277faf1d195000912931b24f2eca9656afe2
child 775168 e04979dd66be1709557e2aa09deb3f838d449a29
push id104638
push userbmo:jlorenzo@mozilla.com
push dateFri, 30 Mar 2018 13:27:33 +0000
reviewersMattN
bugs1444240
milestone61.0a1
Bug 1444240 - Filter address options to eliminate dupes based on fields to display. r=MattN * Add an observed attribute to address-picker to render when address-fields is changed * Add a filter step to address-picker, to exclude addresses that represent duplicates given the requested fields * Filter out addresses that do not contain one or more of the requested fields * Use a default set of fields to establish duplicates and meet the bar for adding an address as an option MozReview-Commit-ID: 7lg4suNHv60
toolkit/components/payments/res/containers/address-picker.js
toolkit/components/payments/res/debugging.html
toolkit/components/payments/res/debugging.js
toolkit/components/payments/test/mochitest/test_payer_address_picker.html
--- a/toolkit/components/payments/res/containers/address-picker.js
+++ b/toolkit/components/payments/res/containers/address-picker.js
@@ -8,31 +8,92 @@
 
 /**
  * <address-picker></address-picker>
  * Container around <rich-select> (eventually providing add/edit links) with
  * <address-option> listening to savedAddresses.
  */
 
 class AddressPicker extends PaymentStateSubscriberMixin(HTMLElement) {
+  static get observedAttributes() {
+    return ["address-fields"];
+  }
+
   constructor() {
     super();
     this.dropdown = document.createElement("rich-select");
     this.dropdown.addEventListener("change", this);
   }
 
   connectedCallback() {
     this.appendChild(this.dropdown);
     super.connectedCallback();
   }
 
+  attributeChangedCallback(name, oldValue, newValue) {
+    if (oldValue !== newValue) {
+      this.render(this.requestStore.getState());
+    }
+  }
+
+  /**
+   * De-dupe and filter addresses for the given set of fields that will be visible
+   *
+   * @param {object} addresses
+   * @param {array?} fieldNames - optional list of field names that be used when de-duping entries
+   * @returns {object} filtered copy of given addresses
+   */
+  filterAddresses(addresses, fieldNames = [
+    "address-level1",
+    "address-level2",
+    "country",
+    "name",
+    "postal-code",
+    "street-address",
+  ]) {
+    let uniques = new Set();
+    let result = {};
+    for (let [guid, address] of Object.entries(addresses)) {
+      let addressCopy = {};
+      let isMatch;
+      // exclude addresses that are missing any of the requested fields
+      for (let name of fieldNames) {
+        if (address[name]) {
+          isMatch = true;
+          addressCopy[name] = address[name];
+        } else {
+          isMatch = false;
+          break;
+        }
+      }
+      if (isMatch) {
+        let key = JSON.stringify(addressCopy);
+        // exclude duplicated addresses
+        if (!uniques.has(key)) {
+          uniques.add(key);
+          result[guid] = address;
+        }
+      }
+    }
+    return result;
+  }
+
   render(state) {
     let {savedAddresses} = state;
     let desiredOptions = [];
-    for (let [guid, address] of Object.entries(savedAddresses)) {
+    let fieldNames;
+    if (this.hasAttribute("address-fields")) {
+      let names = this.getAttribute("address-fields").split(/\s+/);
+      if (names.length) {
+        fieldNames = names;
+      }
+    }
+    let filteredAddresses = this.filterAddresses(savedAddresses, fieldNames);
+
+    for (let [guid, address] of Object.entries(filteredAddresses)) {
       let optionEl = this.dropdown.getOptionByValue(guid);
       if (!optionEl) {
         optionEl = document.createElement("address-option");
         optionEl.value = guid;
       }
 
       for (let [key, val] of Object.entries(address)) {
         optionEl.setAttribute(key, val);
--- a/toolkit/components/payments/res/debugging.html
+++ b/toolkit/components/payments/res/debugging.html
@@ -22,16 +22,17 @@
         <legend>Payment Options</legend>
         <label><input type="checkbox" autocomplete="off" name="requestPayerName" id="setRequestPayerName">requestPayerName</label>
         <label><input type="checkbox" autocomplete="off" name="requestPayerEmail" id="setRequestPayerEmail">requestPayerEmail</label>
         <label><input type="checkbox" autocomplete="off" name="requestPayerPhone" id="setRequestPayerPhone">requestPayerPhone</label>
         <label><input type="checkbox" autocomplete="off" name="requestShipping" id="setRequestShipping">requestShipping</label>
       </fieldset>
       <h1>Addresses</h1>
       <button id="setAddresses1">Set Addreses 1</button>
+      <button id="setDupesAddresses">Set Duped Addresses</button>
       <button id="delete1Address">Delete 1 Address</button>
       <h1>Payment Methods</h1>
       <button id="setBasicCards1">Set Basic Cards 1</button>
       <button id="delete1Card">Delete 1 Card</button>
       <h1>States</h1>
       <button id="setChangesPrevented">Prevent changes</button>
       <button id="setChangesAllowed">Allow changes</button>
       <button id="setShippingError">Shipping Error</button>
--- a/toolkit/components/payments/res/debugging.js
+++ b/toolkit/components/payments/res/debugging.js
@@ -157,16 +157,63 @@ let ADDRESSES_1 = {
     "guid": "68gjdh354j",
     "name": "Mrs. Bar",
     "postal-code": "94041",
     "street-address": "P.O. Box 123",
     "tel": "+1 650 555-5555",
   },
 };
 
+let DUPED_ADDRESSES = {
+  "a9e830667189": {
+    "street-address": "Unit 1\n1505 Northeast Kentucky Industrial Parkway \n",
+    "address-level2": "Greenup",
+    "address-level1": "KY",
+    "postal-code": "41144",
+    "country": "US",
+    "email": "bob@example.com",
+    "guid": "a9e830667189",
+    "tel": "+19871234567",
+    "name": "Bob Smith",
+  },
+  "72a15aed206d": {
+    "street-address": "1 New St",
+    "address-level2": "York",
+    "address-level1": "SC",
+    "postal-code": "29745",
+    "country": "US",
+    "guid": "72a15aed206d",
+    "tel": "+19871234567",
+    "name": "Mary Sue",
+    "address-line1": "1 New St",
+  },
+  "2b4dce0fbc1f": {
+    "street-address": "123 Park St",
+    "address-level2": "Springfield",
+    "address-level1": "OR",
+    "postal-code": "97403",
+    "country": "US",
+    "email": "rita@foo.com",
+    "guid": "2b4dce0fbc1f",
+    "name": "Rita Foo",
+    "address-line1": "123 Park St",
+  },
+  "46b2635a5b26": {
+    "street-address": "432 Another St",
+    "address-level2": "Springfield",
+    "address-level1": "OR",
+    "postal-code": "97402",
+    "country": "US",
+    "email": "rita@foo.com",
+    "guid": "46b2635a5b26",
+    "name": "Rita Foo",
+    "address-line1": "432 Another St",
+  },
+};
+
 let BASIC_CARDS_1 = {
   "53f9d009aed2": {
     "cc-number": "************5461",
     "guid": "53f9d009aed2",
     "version": 1,
     "timeCreated": 1505240896213,
     "timeLastModified": 1515609524588,
     "timeLastUsed": 0,
@@ -236,16 +283,20 @@ let buttonActions = {
   rerender() {
     requestStore.setState({});
   },
 
   setAddresses1() {
     paymentDialog.setStateFromParent({savedAddresses: ADDRESSES_1});
   },
 
+  setDupesAddresses() {
+    paymentDialog.setStateFromParent({savedAddresses: DUPED_ADDRESSES});
+  },
+
   setBasicCards1() {
     paymentDialog.setStateFromParent({savedBasicCards: BASIC_CARDS_1});
   },
 
   setChangesAllowed() {
     requestStore.setState({
       changesPrevented: false,
     });
--- a/toolkit/components/payments/test/mochitest/test_payer_address_picker.html
+++ b/toolkit/components/payments/test/mochitest/test_payer_address_picker.html
@@ -80,16 +80,59 @@ const SAVED_ADDRESSES = {
     "name": "Mrs. Bar",
     "postal-code": "94041",
     "street-address": "P.O. Box 123",
     "tel": "+1 650 555-5555",
     "email": "bar@example.com",
   },
 };
 
+let DUPED_ADDRESSES = {
+  "a9e830667189": {
+    "street-address": "Unit 1\n1505 Northeast Kentucky Industrial Parkway \n",
+    "address-level2": "Greenup",
+    "address-level1": "KY",
+    "postal-code": "41144",
+    "country": "US",
+    "email": "bob@example.com",
+    "guid": "a9e830667189",
+    "name": "Bob Smith",
+  },
+  "72a15aed206d": {
+    "street-address": "1 New St",
+    "address-level2": "York",
+    "address-level1": "SC",
+    "postal-code": "29745",
+    "country": "US",
+    "guid": "72a15aed206d",
+    "email": "mary@example.com",
+    "name": "Mary Sue",
+  },
+  "2b4dce0fbc1f": {
+    "street-address": "123 Park St",
+    "address-level2": "Springfield",
+    "address-level1": "OR",
+    "postal-code": "97403",
+    "country": "US",
+    "email": "rita@foo.com",
+    "guid": "2b4dce0fbc1f",
+    "name": "Rita Foo",
+  },
+  "46b2635a5b26": {
+    "street-address": "432 Another St",
+    "address-level2": "Springfield",
+    "address-level1": "OR",
+    "postal-code": "97402",
+    "country": "US",
+    "guid": "46b2635a5b26",
+    "name": "Rita Foo",
+    "tel": "+19871234567",
+  },
+};
+
 let elPicker;
 let elDialog;
 let initialState;
 
 add_task(async function setup_once() {
   let templateFrame = document.getElementById("templateFrame");
   await SimpleTest.promiseFocus(templateFrame.contentWindow);
 
@@ -196,12 +239,76 @@ add_task(async function test_selective_f
     is(isVisible(elName), payerFields.requestPayerName,
        "name field is correctly toggled");
     is(isVisible(elEmail), payerFields.requestPayerEmail,
        "email field is correctly toggled");
     is(isVisible(elPhone), payerFields.requestPayerPhone,
        "tel field is correctly toggled");
   }
 });
+
+add_task(async function test_filtered_options() {
+  await setup();
+  let requestStore = elPicker.requestStore;
+  setPaymentOptions(requestStore, {
+    requestPayerName: true,
+    requestPayerEmail: true,
+  });
+
+  requestStore.setState({
+    savedAddresses: DUPED_ADDRESSES,
+    selectedPayerAddress: "a9e830667189",
+  });
+
+  await asyncElementRendered();
+
+  let visibleOptions = getVisiblePickerOptions(elPicker);
+  let visibleOption = visibleOptions[0];
+
+  is(elPicker.dropdown.popupBox.children.length, 3, "Check dropdown has 3 addresses");
+  is(visibleOptions.length, 1, "One option should be visible");
+  is(visibleOption.getAttribute("guid"), "a9e830667189", "expected option is visible");
+
+  for (let fieldName of ["name", "email"]) {
+    let elem = visibleOption.querySelector(`.${fieldName}`);
+    ok(elem, `field ${fieldName} exists`);
+    ok(isVisible(elem), `field ${fieldName} is visible`);
+  }
+
+  setPaymentOptions(requestStore, {
+    requestPayerPhone: true,
+  });
+  await asyncElementRendered();
+
+  is(elPicker.dropdown.popupBox.children.length, 1, "Check dropdown has 1 addresses");
+  is(visibleOptions.length, 1, "One option should be visible");
+
+  setPaymentOptions(requestStore, {});
+  await asyncElementRendered();
+
+  is(elPicker.dropdown.popupBox.children.length, 4, "Check dropdown has 4 addresses");
+  is(visibleOptions.length, 1, "One option should be visible");
+});
+
+add_task(async function test_no_matches() {
+  await setup();
+  let requestStore = elPicker.requestStore;
+  setPaymentOptions(requestStore, {
+    requestPayerEmail: true,
+    requestPayerPhone: true,
+  });
+
+  requestStore.setState({
+    savedAddresses: DUPED_ADDRESSES,
+    selectedPayerAddress: "a9e830667189",
+  });
+
+  await asyncElementRendered();
+
+  let visibleOptions = getVisiblePickerOptions(elPicker);
+  is(elPicker.dropdown.popupBox.children.length, 0, "Check dropdown is empty");
+  is(visibleOptions.length, 0, "No options should be visible");
+});
+
 </script>
 
 </body>
 </html>