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
--- 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>