Bug 1428472 - Default to the selected shipping address on the "add basic card" screen. r=jaws draft
authorMatthew Noorenberghe <mozilla@noorenberghe.ca>
Fri, 13 Apr 2018 17:25:13 -0700
changeset 782060 a5ab0487d554ed906eb20e93a2d42de218a9293d
parent 782059 810565ff7a63b2c0d4e89899f73971d6da74df30
push id106470
push usermozilla@noorenberghe.ca
push dateSat, 14 Apr 2018 01:32:47 +0000
reviewersjaws
bugs1428472
milestone61.0a1
Bug 1428472 - Default to the selected shipping address on the "add basic card" screen. r=jaws MozReview-Commit-ID: Hx4AHqiTapn
toolkit/components/payments/res/PaymentsStore.js
toolkit/components/payments/res/containers/basic-card-form.js
toolkit/components/payments/res/containers/payment-dialog.js
toolkit/components/payments/res/debugging.js
toolkit/components/payments/test/browser/browser_change_shipping.js
toolkit/components/payments/test/mochitest/mochitest.ini
toolkit/components/payments/test/mochitest/test_basic_card_form.html
--- a/toolkit/components/payments/res/PaymentsStore.js
+++ b/toolkit/components/payments/res/PaymentsStore.js
@@ -8,16 +8,17 @@
  * state propagation.
  */
 
 export default class PaymentsStore {
   /**
    * @param {object} [defaultState = {}] The initial state of the store.
    */
   constructor(defaultState = {}) {
+    this._defaultState = Object.assign({}, defaultState);
     this._state = defaultState;
     this._nextNotifification = 0;
     this._subscribers = new Set();
   }
 
   /**
    * Get the current state as a shallow clone with a shallow freeze.
    * You shouldn't modify any part of the returned state object as that would bypass notifying
@@ -25,16 +26,24 @@ export default class PaymentsStore {
    *
    * @returns {Object} containing the current state
    */
   getState() {
     return Object.freeze(Object.assign({}, this._state));
   }
 
   /**
+   * Used for testing to reset to the default state from the constructor.
+   * @returns {Promise} returned by setState.
+   */
+  async reset() {
+    return this.setState(this._defaultState);
+  }
+
+  /**
    * Augment the current state with the keys of `obj` and asynchronously notify
    * state subscribers. As a result, multiple synchronous state changes will lead
    * to a single subscriber notification which leads to better performance and
    * reduces partial state changes.
    *
    * @param {Object} obj The object to augment the state with. Keys in the object
    *                     will be shallow copied with Object.assign.
    *
--- a/toolkit/components/payments/res/containers/basic-card-form.js
+++ b/toolkit/components/payments/res/containers/basic-card-form.js
@@ -73,29 +73,32 @@ export default class BasicCardForm exten
     this.backButton.textContent = this.dataset.backButtonLabel;
     this.saveButton.textContent = this.dataset.saveButtonLabel;
 
     let record = {};
     let {
       page,
       savedAddresses,
       savedBasicCards,
+      selectedShippingAddress,
     } = state;
 
     this.genericErrorText.textContent = page.error;
 
     let editing = !!page.guid;
     this.form.querySelector("#cc-number").disabled = editing;
 
     // If a card is selected we want to edit it.
     if (editing) {
       record = savedBasicCards[page.guid];
       if (!record) {
         throw new Error("Trying to edit a non-existing card: " + page.guid);
       }
+    } else if (selectedShippingAddress) {
+      record.billingAddressGUID = selectedShippingAddress;
     }
 
     this.formHandler.loadRecord(record, savedAddresses);
   }
 
   handleEvent(event) {
     switch (event.type) {
       case "click": {
--- a/toolkit/components/payments/res/containers/payment-dialog.js
+++ b/toolkit/components/payments/res/containers/payment-dialog.js
@@ -9,16 +9,17 @@ import PaymentStateSubscriberMixin from 
 import "../components/currency-amount.js";
 import "./address-picker.js";
 import "./basic-card-form.js";
 import "./order-details.js";
 import "./payment-method-picker.js";
 import "./shipping-option-picker.js";
 
 /* global paymentRequest */
+/* import-globals-from ../unprivileged-fallbacks.js */
 
 /**
  * <payment-dialog></payment-dialog>
  */
 
 export default class PaymentDialog extends PaymentStateSubscriberMixin(HTMLElement) {
   constructor() {
     super();
@@ -116,16 +117,17 @@ export default class PaymentDialog exten
    */
   setStateFromParent(state) {
     let oldSavedAddresses = this.requestStore.getState().savedAddresses;
     this.requestStore.setState(state);
 
     // Check if any foreign-key constraints were invalidated.
     state = this.requestStore.getState();
     let {
+      request: {paymentOptions: {requestShipping: requestShipping}},
       savedAddresses,
       savedBasicCards,
       selectedPayerAddress,
       selectedPaymentCard,
       selectedShippingAddress,
       selectedShippingOption,
     } = state;
     let shippingOptions = state.request.paymentDetails.shippingOptions;
@@ -141,18 +143,23 @@ export default class PaymentDialog exten
       if (oldShippingAddress &&
           shippingAddress.guid == oldShippingAddress.guid &&
           shippingAddress.timeLastModified != oldShippingAddress.timeLastModified) {
         delete this._cachedState.selectedShippingAddress;
       }
     } else {
       // assign selectedShippingAddress as value if it is undefined,
       // or if the address it pointed to was removed from storage
+      let defaultShippingAddress = null;
+      if (requestShipping) {
+        defaultShippingAddress = Object.keys(savedAddresses)[0];
+        log.debug("selecting the default shipping address");
+      }
       this.requestStore.setState({
-        selectedShippingAddress: Object.keys(savedAddresses)[0] || null,
+        selectedShippingAddress: defaultShippingAddress || null,
       });
     }
 
     // Ensure `selectedPaymentCard` never refers to a deleted payment card and refers
     // to a payment card if one exists.
     if (!savedBasicCards[selectedPaymentCard]) {
       this.requestStore.setState({
         selectedPaymentCard: Object.keys(savedBasicCards)[0] || null,
--- a/toolkit/components/payments/res/debugging.js
+++ b/toolkit/components/payments/res/debugging.js
@@ -305,21 +305,21 @@ let buttonActions = {
 
   setChangesPrevented() {
     requestStore.setState({
       changesPrevented: true,
     });
   },
 
   setRequest1() {
-    requestStore.setState({request: REQUEST_1});
+    paymentDialog.setStateFromParent({request: REQUEST_1});
   },
 
   setRequest2() {
-    requestStore.setState({request: REQUEST_2});
+    paymentDialog.setStateFromParent({request: REQUEST_2});
   },
 
   setRequestPayerName() {
     buttonActions.setPaymentOptions();
   },
   setRequestPayerEmail() {
     buttonActions.setPaymentOptions();
   },
--- a/toolkit/components/payments/test/browser/browser_change_shipping.js
+++ b/toolkit/components/payments/test/browser/browser_change_shipping.js
@@ -138,16 +138,17 @@ add_task(async function test_address_edi
     gBrowser,
     url: BLANK_PAGE_URL,
   }, async browser => {
     let {win, frame} =
       await setupPaymentDialog(browser, {
         methodData: [PTU.MethodData.basicCard],
         details: PTU.Details.twoShippingOptions,
         merchantTaskFn: PTU.ContentTasks.createAndShowRequest,
+        options: PTU.Options.requestShippingOption,
       }
     );
 
     let addressOptions =
       await spawnPaymentDialogTask(frame, PTU.DialogContentTasks.getShippingAddresses);
     info("initial addressOptions: " + JSON.stringify(addressOptions));
     let selectedIndex = addressOptions.selectedOptionIndex;
     let selectedAddressGuid = addressOptions.options[selectedIndex].guid;
@@ -191,16 +192,17 @@ add_task(async function test_address_rem
     gBrowser,
     url: BLANK_PAGE_URL,
   }, async browser => {
     let {win, frame} =
       await setupPaymentDialog(browser, {
         methodData: [PTU.MethodData.basicCard],
         details: PTU.Details.twoShippingOptions,
         merchantTaskFn: PTU.ContentTasks.createAndShowRequest,
+        options: PTU.Options.requestShippingOption,
       }
     );
 
     let addressOptions =
       await spawnPaymentDialogTask(frame, PTU.DialogContentTasks.getShippingAddresses);
     info("initial addressOptions: " + JSON.stringify(addressOptions));
     let selectedIndex = addressOptions.selectedOptionIndex;
     let selectedAddressGuid = addressOptions.options[selectedIndex].guid;
--- a/toolkit/components/payments/test/mochitest/mochitest.ini
+++ b/toolkit/components/payments/test/mochitest/mochitest.ini
@@ -1,12 +1,13 @@
 [DEFAULT]
 prefs =
    dom.webcomponents.customelements.enabled=false
 support-files =
+   !/browser/extensions/formautofill/content/editCreditCard.xhtml
    ../../../../../browser/extensions/formautofill/content/autofillEditForms.js
    ../../../../../testing/modules/sinon-2.3.2.js
    ../../res/**
    payments_common.js
 skip-if = !e10s
 
 [test_address_picker.html]
 [test_basic_card_form.html]
--- a/toolkit/components/payments/test/mochitest/test_basic_card_form.html
+++ b/toolkit/components/payments/test/mochitest/test_basic_card_form.html
@@ -34,16 +34,17 @@ Test the basic-card-form element
 /* import-globals-from payments_common.js */
 
 import BasicCardForm from "../../res/containers/basic-card-form.js";
 
 let display = document.getElementById("display");
 
 function checkCCForm(customEl, expectedCard) {
   const CC_PROPERTY_NAMES = [
+    "billingAddressGUID",
     "cc-number",
     "cc-name",
     "cc-exp-month",
     "cc-exp-year",
   ];
   for (let propName of CC_PROPERTY_NAMES) {
     let expectedVal = expectedCard[propName] || "";
     is(document.getElementById(propName).value,
@@ -148,17 +149,92 @@ add_task(async function test_genericErro
   display.appendChild(form);
   await asyncElementRendered();
 
   ok(!isHidden(form.genericErrorText), "Error message should be visible");
   is(form.genericErrorText.textContent, "Generic Error", "Check error message");
   form.remove();
 });
 
-add_task(async function test_record() {
+add_task(async function test_add_selectedShippingAddress() {
+  let form = new BasicCardForm();
+  await form.promiseReady;
+  display.appendChild(form);
+  await asyncElementRendered();
+
+  info("have an existing card in storage");
+  let card1 = deepClone(PTU.BasicCards.JohnDoe);
+  card1.guid = "9864798564";
+  card1["cc-exp-year"] = 2011;
+
+  let address1 = deepClone(PTU.Addresses.TimBL);
+  address1.guid = "TimBLGUID";
+
+  await form.requestStore.setState({
+    page: {
+      id: "basic-card-page",
+    },
+    savedAddresses: {
+      [address1.guid]: deepClone(address1),
+    },
+    savedBasicCards: {
+      [card1.guid]: deepClone(card1),
+    },
+    selectedShippingAddress: address1.guid,
+  });
+  await asyncElementRendered();
+  checkCCForm(form, {
+    billingAddressGUID: address1.guid,
+  });
+
+  form.remove();
+  await form.requestStore.reset();
+});
+
+add_task(async function test_add_noSelectedShippingAddress() {
+  let form = new BasicCardForm();
+  await form.promiseReady;
+  display.appendChild(form);
+  await asyncElementRendered();
+
+  info("have an existing card in storage but unused");
+  let card1 = deepClone(PTU.BasicCards.JohnDoe);
+  card1.guid = "9864798564";
+  card1["cc-exp-year"] = 2011;
+
+  let address1 = deepClone(PTU.Addresses.TimBL);
+  address1.guid = "TimBLGUID";
+
+  await form.requestStore.setState({
+    page: {
+      id: "basic-card-page",
+    },
+    savedAddresses: {
+      [address1.guid]: deepClone(address1),
+    },
+    savedBasicCards: {
+      [card1.guid]: deepClone(card1),
+    },
+    selectedShippingAddress: null,
+  });
+  await asyncElementRendered();
+  checkCCForm(form, {});
+
+  info("now test with a missing selectedShippingAddress");
+  await form.requestStore.setState({
+    selectedShippingAddress: "some-missing-guid",
+  });
+  await asyncElementRendered();
+  checkCCForm(form, {});
+
+  form.remove();
+  await form.requestStore.reset();
+});
+
+add_task(async function test_edit() {
   let form = new BasicCardForm();
   await form.promiseReady;
   display.appendChild(form);
   await asyncElementRendered();
 
   info("test year before current");
   let card1 = deepClone(PTU.BasicCards.JohnDoe);
   card1.guid = "9864798564";