Bug 1440499 - Add payerName/Email/Phone contact picker.
* WIP, tests to ensure the paymentOptions values propagate to the picker underway
* Add a field-names attribute to the payer address-picker, populated from the request paymentOptions
* Initial/placeholder CSS to selectively render address fields
MozReview-Commit-ID: Br8i5MVyeQ3
--- a/toolkit/components/payments/res/components/address-option.css
+++ b/toolkit/components/payments/res/components/address-option.css
@@ -12,35 +12,50 @@ address-option {
rich-select[open] > .rich-select-popup-box > address-option {
grid-template-areas:
"name name "
"street-address street-address"
"email tel ";
}
+address-picker.payer-related address-option {
+ grid-template-areas:
+ "name name"
+ "tel email";
+}
+
address-option > .name {
grid-area: name;
}
address-option > .street-address {
grid-area: street-address;
}
address-option > .email {
grid-area: email;
}
address-option > .tel {
grid-area: tel;
}
+/* Hide all the fields by default, and enable them explicitly for each format case */
address-option > .name,
address-option > .street-address,
address-option > .email,
address-option > .tel {
white-space: nowrap;
+ display: none;
}
-.rich-select-selected-clone > .email,
-.rich-select-selected-clone > .tel {
- display: none;
+address-picker:not([address-fields]) address-option > .name,
+address-picker:not([address-fields]) address-option > .street-address {
+ display: inline-block;
}
+
+address-picker[address-fields~='name'] address-option > .name,
+address-picker[address-fields~='email'] address-option > .email,
+address-picker[address-fields~='tel'] address-option > .tel {
+ display: inline-block;
+}
+
--- a/toolkit/components/payments/res/containers/address-picker.js
+++ b/toolkit/components/payments/res/containers/address-picker.js
@@ -28,21 +28,23 @@ class AddressPicker extends PaymentState
let {savedAddresses} = state;
let desiredOptions = [];
for (let [guid, address] of Object.entries(savedAddresses)) {
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);
}
desiredOptions.push(optionEl);
}
+
let el = null;
while ((el = this.dropdown.popupBox.querySelector(":scope > address-option"))) {
el.remove();
}
for (let option of desiredOptions) {
this.dropdown.popupBox.appendChild(option);
}
--- a/toolkit/components/payments/res/containers/payment-dialog.js
+++ b/toolkit/components/payments/res/containers/payment-dialog.js
@@ -29,16 +29,18 @@ class PaymentDialog extends PaymentState
this._viewAllButton = contents.querySelector("#view-all");
this._viewAllButton.addEventListener("click", this);
this._orderDetailsOverlay = contents.querySelector("#order-details-overlay");
this._shippingTypeLabel = contents.querySelector("#shipping-type-label");
this._shippingRelatedEls = contents.querySelectorAll(".shipping-related");
this._payerRelatedEls = contents.querySelectorAll(".payer-related");
+ this._payerAddressPicker = contents.querySelector("address-picker.payer-related");
+
this._errorText = contents.querySelector("#error-text");
this._disabledOverlay = contents.getElementById("disabled-overlay");
this.appendChild(contents);
super.connectedCallback();
}
@@ -204,26 +206,44 @@ class PaymentDialog extends PaymentState
let totalItem = paymentDetails.totalItem;
let totalAmountEl = this.querySelector("#total > currency-amount");
totalAmountEl.value = totalItem.amount.value;
totalAmountEl.currency = totalItem.amount.currency;
this._orderDetailsOverlay.hidden = !state.orderDetailsShowing;
this._errorText.textContent = paymentDetails.error;
+
let paymentOptions = request.paymentOptions;
for (let element of this._shippingRelatedEls) {
element.hidden = !paymentOptions.requestShipping;
}
let payerRequested = paymentOptions.requestPayerName ||
paymentOptions.requestPayerEmail ||
paymentOptions.requestPayerPhone;
for (let element of this._payerRelatedEls) {
element.hidden = !payerRequested;
}
+
+ if (payerRequested) {
+ let fieldNames = new Set(); // default: ["name", "tel", "email"]
+ if (paymentOptions.requestPayerName) {
+ fieldNames.add("name");
+ }
+ if (paymentOptions.requestPayerEmail) {
+ fieldNames.add("email");
+ }
+ if (paymentOptions.requestPayerPhone) {
+ fieldNames.add("tel");
+ }
+ this._payerAddressPicker.setAttribute("address-fields", [...fieldNames].join(" "));
+ } else {
+ this._payerAddressPicker.removeAttribute("address-fields");
+ }
+
let shippingType = paymentOptions.shippingType || "shipping";
this._shippingTypeLabel.querySelector("label").textContent =
this._shippingTypeLabel.dataset[shippingType + "AddressLabel"];
this._renderPayButton(state);
let {
changesPrevented,
--- a/toolkit/components/payments/test/mochitest/mochitest.ini
+++ b/toolkit/components/payments/test/mochitest/mochitest.ini
@@ -26,15 +26,16 @@ support-files =
../../res/mixins/PaymentStateSubscriberMixin.js
../../res/vendor/custom-elements.min.js
../../res/vendor/custom-elements.min.js.map
payments_common.js
[test_address_picker.html]
[test_currency_amount.html]
[test_order_details.html]
+[test_payer_address_picker.html]
[test_payment_dialog.html]
[test_payment_details_item.html]
[test_payment_method_picker.html]
[test_rich_select.html]
[test_shipping_option_picker.html]
[test_ObservedPropertiesMixin.html]
[test_PaymentStateSubscriberMixin.html]
new file mode 100644
--- /dev/null
+++ b/toolkit/components/payments/test/mochitest/test_payer_address_picker.html
@@ -0,0 +1,174 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test the paymentOptions address-picker
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test the paymentOptions address-picker</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
+ <script src="sinon-2.3.2.js"></script>
+ <script src="payments_common.js"></script>
+
+ <script src="custom-elements.min.js"></script>
+ <script src="PaymentsStore.js"></script>
+ <script src="ObservedPropertiesMixin.js"></script>
+ <script src="PaymentStateSubscriberMixin.js"></script>
+ <script src="payment-dialog.js"></script>
+
+ <script src="rich-select.js"></script>
+ <script src="address-picker.js"></script>
+ <script src="rich-option.js"></script>
+ <script src="address-option.js"></script>
+ <script src="currency-amount.js"></script>
+ <link rel="stylesheet" type="text/css" href="rich-select.css"/>
+ <link rel="stylesheet" type="text/css" href="address-option.css"/>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <link rel="stylesheet" type="text/css" href="paymentRequest.css"/>
+</head>
+<body>
+ <p id="display">
+ <iframe id="templateFrame" src="paymentRequest.xhtml" width="0" height="0"></iframe>
+ </p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+<script type="application/javascript">
+/** Test the payer requested details functionality **/
+
+// (WIP) test that:
+// payer picker is not shown when there are no addresses
+// payer picker is not shown when all paymentOptions are false
+// payer picker is shown when one or more paymentOptions is given AND when an address is available
+// the correct fields are visible to correspond with paymentOptions values.
+
+
+/* global sinon */
+/* import-globals-from payments_common.js */
+/* import-globals-from ../../res/mixins/PaymentStateSubscriberMixin.js */
+
+function getVisiblePickerOptions(picker) {
+ let select = picker.querySelector(":scope > rich-select");
+ // let popupBox = Cu.waiveXrays(select).popupBox;
+ let popupBox = select.popupBox;
+ let visibleOptions = Array.from(popupBox.children).filter(item => {
+ return item.getBoundingClientRect().height > 0;
+ });
+ // Cu.unwaiveXrays(picker);
+ return visibleOptions;
+}
+
+function isVisible(elem) {
+ return elem.getBoundingClientRect().height > 0;
+}
+
+const SAVED_ADDRESSES = {
+ "48bnds6854t": {
+ "address-level1": "MI",
+ "address-level2": "Some City",
+ "country": "US",
+ "guid": "48bnds6854t",
+ "name": "Mr. Foo",
+ "postal-code": "90210",
+ "street-address": "123 Sesame Street,\nApt 40",
+ "tel": "+1 519 555-5555",
+ },
+ "68gjdh354j": {
+ "address-level1": "CA",
+ "address-level2": "Mountain View",
+ "country": "US",
+ "guid": "68gjdh354j",
+ "name": "Mrs. Bar",
+ "postal-code": "94041",
+ "street-address": "P.O. Box 123",
+ "tel": "+1 650 555-5555",
+ },
+};
+
+let picker1;
+let el1;
+
+add_task(async function setup_once() {
+ let templateFrame = document.getElementById("templateFrame");
+ await SimpleTest.promiseFocus(templateFrame.contentWindow);
+
+ let displayEl = document.getElementById("display");
+ // Import the templates from the real shipping dialog to avoid duplication.
+ for (let template of templateFrame.contentDocument.querySelectorAll("template")) {
+ let imported = document.importNode(template, true);
+ displayEl.appendChild(imported);
+ }
+
+ el1 = document.createElement("payment-dialog");
+ displayEl.appendChild(el1);
+
+ sinon.spy(el1, "render");
+ sinon.spy(el1, "stateChangeCallback");
+});
+
+async function setup() {
+ el1.requestStore.setState({
+ changesPrevented: false,
+ completionState: "initial",
+ orderDetailsShowing: false,
+ });
+ await asyncElementRendered();
+ el1.render.reset();
+ el1.stateChangeCallback.reset();
+ picker1 = el1.querySelector("address-picker.payer-related");
+}
+
+add_task(async function test_empty() {
+ await setup();
+
+ ok(picker1, "Check picker1 exists");
+ let state = picker1.requestStore.getState();
+ let {paymentOptions} = state && state.request;
+ let payerRequested = paymentOptions.requestPayerName ||
+ paymentOptions.requestPayerEmail ||
+ paymentOptions.requestPayerPhone;
+ ok(!payerRequested, "Check initial state has no payer details requested");
+ is(picker1.dropdown.popupBox.children.length, 0, "Check dropdown is empty");
+});
+
+// paymentOptions properties are acurately reflected in the address-fields attribute
+add_task(async function test_visible_fields() {
+ await setup();
+ let request = picker1.requestStore.getState().request;
+ request = Object.assign({}, request, {
+ paymentOptions: {
+ requestPayerName: true,
+ requestPayerEmail: true,
+ requestPayerPhone: true,
+ },
+ });
+ picker1.requestStore.setState({
+ savedAddresses: SAVED_ADDRESSES,
+ selectedPayerAddress: "48bnds6854t",
+ request,
+ });
+
+ await asyncElementRendered();
+
+ is(picker1.dropdown.popupBox.children.length, 2, "Check dropdown has 2 addresses");
+
+ let visibleOptions = getVisiblePickerOptions(picker1);
+ let visibleOption = visibleOptions[0];
+ is(visibleOptions.length, 1, "One option should be visible");
+ is(visibleOption.getAttribute("guid"), "48bnds6854t", "expected option is visible");
+
+ for (let fieldName of ["name", "email", "tel"]) {
+ let elem = visibleOption.querySelector(`.${fieldName}`);
+ ok(elem, `field ${fieldName} exists`);
+ ok(isVisible(elem), `field ${fieldName} is visible`);
+ }
+ ok(!isVisible(visibleOption.querySelector(".street-address")), "street-address is not visible");
+});
+
+</script>
+
+</body>
+</html>