Bug 1428472 - Default to the selected shipping address on the "add basic card" screen. r=jaws
authorMatthew Noorenberghe <mozilla@noorenberghe.ca>
Fri, 13 Apr 2018 17:25:13 -0700
changeset 467506 69383ad18409381ba12aa29c84d550eaa07b75d0
parent 467505 f22a918616f8457e25115c54e3517fc18265d8e2
child 467507 a8aeb060a6c8fb048a334ea9bc6b2de0fe8d3bf2
push id9165
push userasasaki@mozilla.com
push dateThu, 26 Apr 2018 21:04:54 +0000
treeherdermozilla-beta@064c3804de2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjaws
bugs1428472
milestone61.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 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";