Bug 1432927 - Show a payment card input form before the summary view for users without a saved payment card. r?MattN draft
authorprathiksha <prathikshaprasadsuman@gmail.com>
Fri, 11 May 2018 12:26:54 -0700
changeset 796622 3e178bd4d80d54772ff493ec14999198a59b66a9
parent 795989 f7a4cb6a3b4da0c5ea136ef4e922cc66feb82f35
child 797255 ca5dcf0a16c498aa9a4794552fd1077d64fc745c
child 797291 52c21b043813d47f200335bafc67e826fd663ac7
child 797292 e5554acef6f7fd4fd2de7c6defe90386a888797a
child 797325 cb423b8a5574094a975ce1714e1d39e6ead8099a
child 797827 26b19a4c53718012530aca185fa94075536421a9
push id110316
push userbmo:prathikshaprasadsuman@gmail.com
push dateThu, 17 May 2018 23:43:52 +0000
reviewersMattN
bugs1432927
milestone62.0a1
Bug 1432927 - Show a payment card input form before the summary view for users without a saved payment card. r?MattN MozReview-Commit-ID: BmGG8OFzCjJ
browser/components/payments/res/containers/address-form.js
browser/components/payments/res/containers/basic-card-form.js
browser/components/payments/res/containers/payment-method-picker.js
browser/components/payments/res/paymentRequest.js
browser/components/payments/res/paymentRequest.xhtml
browser/components/payments/test/browser/browser_payments_onboarding_wizard.js
--- a/browser/components/payments/res/containers/address-form.js
+++ b/browser/components/payments/res/containers/address-form.js
@@ -17,24 +17,25 @@ import paymentRequest from "../paymentRe
 export default class AddressForm extends PaymentStateSubscriberMixin(HTMLElement) {
   constructor() {
     super();
 
     this.pageTitle = document.createElement("h1");
     this.genericErrorText = document.createElement("div");
 
     this.cancelButton = document.createElement("button");
-    this.cancelButton.id = "address-page-cancel-button";
+    this.cancelButton.className = "cancel-button";
     this.cancelButton.addEventListener("click", this);
 
     this.backButton = document.createElement("button");
     this.backButton.className = "back-button";
     this.backButton.addEventListener("click", this);
 
     this.saveButton = document.createElement("button");
+    this.saveButton.className = "save-button";
     this.saveButton.addEventListener("click", this);
 
     // The markup is shared with form autofill preferences.
     let url = "formautofill/editAddress.xhtml";
     this.promiseReady = this._fetchMarkup(url).then(doc => {
       this.form = doc.getElementById("form");
       return this.form;
     });
@@ -84,16 +85,17 @@ export default class AddressForm extends
 
     let record = {};
     let {
       page,
       savedAddresses,
     } = state;
 
     this.backButton.hidden = page.onboardingWizard;
+    this.cancelButton.hidden = !page.onboardingWizard;
 
     if (page.addressFields) {
       this.setAttribute("address-fields", page.addressFields);
     } else {
       this.removeAttribute("address-fields");
     }
 
     this.pageTitle.textContent = page.title;
@@ -144,30 +146,44 @@ export default class AddressForm extends
       }
     }
   }
 
   saveRecord() {
     let record = this.formHandler.buildFormObject();
     let {
       page,
+      savedBasicCards,
     } = this.requestStore.getState();
 
-    paymentRequest.updateAutofillRecord("addresses", record, page.guid, {
+    let state = {
       errorStateChange: {
         page: {
           id: "address-page",
           onboardingWizard: page.onboardingWizard,
           error: this.dataset.errorGenericSave,
         },
       },
       preserveOldProperties: true,
       selectedStateKey: page.selectedStateKey,
-      successStateChange: {
+    };
+
+    if (page.onboardingWizard && !Object.keys(savedBasicCards).length) {
+      state.successStateChange = {
+        page: {
+          id: "basic-card-page",
+          onboardingWizard: true,
+          guid: null,
+        },
+      };
+    } else {
+      state.successStateChange = {
         page: {
           id: "payment-summary",
         },
-      },
-    });
+      };
+    }
+
+    paymentRequest.updateAutofillRecord("addresses", record, page.guid, state);
   }
 }
 
 customElements.define("address-form", AddressForm);
--- a/browser/components/payments/res/containers/basic-card-form.js
+++ b/browser/components/payments/res/containers/basic-card-form.js
@@ -18,21 +18,26 @@ import paymentRequest from "../paymentRe
 
 export default class BasicCardForm extends PaymentStateSubscriberMixin(HTMLElement) {
   constructor() {
     super();
 
     this.pageTitle = document.createElement("h1");
     this.genericErrorText = document.createElement("div");
 
+    this.cancelButton = document.createElement("button");
+    this.cancelButton.className = "cancel-button";
+    this.cancelButton.addEventListener("click", this);
+
     this.backButton = document.createElement("button");
     this.backButton.className = "back-button";
     this.backButton.addEventListener("click", this);
 
     this.saveButton = document.createElement("button");
+    this.saveButton.className = "save-button";
     this.saveButton.addEventListener("click", this);
 
     this.persistCheckbox = new LabelledCheckbox();
 
     // The markup is shared with form autofill preferences.
     let url = "formautofill/editCreditCard.xhtml";
     this.promiseReady = this._fetchMarkup(url).then(doc => {
       this.form = doc.getElementById("form");
@@ -64,53 +69,60 @@ export default class BasicCardForm exten
         form,
       }, record, addresses, {
         isCCNumber: PaymentDialogUtils.isCCNumber,
         getAddressLabel: PaymentDialogUtils.getAddressLabel,
       });
 
       this.appendChild(this.persistCheckbox);
       this.appendChild(this.genericErrorText);
+      this.appendChild(this.cancelButton);
       this.appendChild(this.backButton);
       this.appendChild(this.saveButton);
       // Only call the connected super callback(s) once our markup is fully
       // connected, including the shared form fetched asynchronously.
       super.connectedCallback();
     });
   }
 
   render(state) {
     let {
       page,
       savedAddresses,
       selectedShippingAddress,
     } = state;
 
-    this.pageTitle.textContent = page.title;
+    this.cancelButton.textContent = this.dataset.cancelButtonLabel;
     this.backButton.textContent = this.dataset.backButtonLabel;
     this.saveButton.textContent = this.dataset.saveButtonLabel;
     this.persistCheckbox.label = this.dataset.persistCheckboxLabel;
 
+    // The back button is temporarily hidden(See Bug 1462461).
+    this.backButton.hidden = !!page.onboardingWizard;
+    this.cancelButton.hidden = !page.onboardingWizard;
+
     let record = {};
     let basicCards = paymentRequest.getBasicCards(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) {
+      this.pageTitle.textContent = this.dataset.editBasicCardTitle;
       record = basicCards[page.guid];
       if (!record) {
         throw new Error("Trying to edit a non-existing card: " + page.guid);
       }
       // When editing an existing record, prevent changes to persistence
       this.persistCheckbox.hidden = true;
     } else {
+      this.pageTitle.textContent = this.dataset.addBasicCardTitle;
       if (selectedShippingAddress) {
         record.billingAddressGUID = selectedShippingAddress;
       }
       // Adding a new record: default persistence to checked when in a not-private session
       this.persistCheckbox.hidden = false;
       this.persistCheckbox.checked = !state.isPrivate;
     }
 
@@ -123,16 +135,20 @@ export default class BasicCardForm exten
         this.onClick(event);
         break;
       }
     }
   }
 
   onClick(evt) {
     switch (evt.target) {
+      case this.cancelButton: {
+        paymentRequest.cancel();
+        break;
+      }
       case this.backButton: {
         this.requestStore.setState({
           page: {
             id: "payment-summary",
           },
         });
         break;
       }
--- a/browser/components/payments/res/containers/payment-method-picker.js
+++ b/browser/components/payments/res/containers/payment-method-picker.js
@@ -133,24 +133,22 @@ export default class PaymentMethodPicker
       page: {
         id: "basic-card-page",
       },
     };
 
     switch (target) {
       case this.addLink: {
         nextState.page.guid = null;
-        nextState.page.title = this.dataset.addBasicCardTitle;
         break;
       }
       case this.editLink: {
         let state = this.requestStore.getState();
         let selectedPaymentCardGUID = state[this.selectedStateKey];
         nextState.page.guid = selectedPaymentCardGUID;
-        nextState.page.title = this.dataset.editBasicCardTitle;
         break;
       }
       default: {
         throw new Error("Unexpected onClick");
       }
     }
 
     this.requestStore.setState(nextState);
--- a/browser/components/payments/res/paymentRequest.js
+++ b/browser/components/payments/res/paymentRequest.js
@@ -125,16 +125,22 @@ var paymentRequest = {
       },
     };
 
     if (Object.keys(detail.savedAddresses).length == 0) {
       state.page = {
         id: "address-page",
         onboardingWizard: true,
       };
+    } else if (Object.keys(detail.savedBasicCards).length == 0) {
+      state.page = {
+        id: "basic-card-page",
+        onboardingWizard: true,
+        guid: null,
+      };
     }
 
     document.querySelector("payment-dialog").setStateFromParent(state);
   },
 
   cancel() {
     this.sendMessageToChrome("paymentCancel");
   },
--- a/browser/components/payments/res/paymentRequest.xhtml
+++ b/browser/components/payments/res/paymentRequest.xhtml
@@ -99,18 +99,16 @@
                           data-edit-link-label="&address.editLink.label;"
                           selected-state-key="selectedShippingAddress"></address-picker>
 
           <div class="shipping-related"><label>&shippingOptionsLabel;</label></div>
           <shipping-option-picker class="shipping-related"></shipping-option-picker>
 
           <div><label>&paymentMethodsLabel;</label></div>
           <payment-method-picker selected-state-key="selectedPaymentCard"
-                                 data-add-basic-card-title="&basicCard.addPage.title;"
-                                 data-edit-basic-card-title="&basicCard.editPage.title;"
                                  data-add-link-label="&basicCard.addLink.label;"
                                  data-edit-link-label="&basicCard.editLink.label;">
           </payment-method-picker>
 
           <div class="payer-related"><label>&payerLabel;</label></div>
           <address-picker class="payer-related"
                           data-add-link-label="&payer.addLink.label;"
                           data-edit-link-label="&payer.editLink.label;"
@@ -130,19 +128,22 @@
       </section>
       <section id="order-details-overlay" hidden="hidden">
         <h1>&orderDetailsLabel;</h1>
         <order-details></order-details>
       </section>
 
       <basic-card-form id="basic-card-page"
                        class="page"
+                       data-add-basic-card-title="&basicCard.addPage.title;"
+                       data-edit-basic-card-title="&basicCard.editPage.title;"
                        data-error-generic-save="&basicCardPage.error.genericSave;"
                        data-back-button-label="&basicCardPage.backButton.label;"
                        data-save-button-label="&basicCardPage.saveButton.label;"
+                       data-cancel-button-label="&cancelPaymentButton.label;"
                        data-persist-checkbox-label="&basicCardPage.persistCheckbox.label;"
                        hidden="hidden"></basic-card-form>
 
       <address-form id="address-page"
                     class="page"
                     data-error-generic-save="&addressPage.error.genericSave;"
                     data-cancel-button-label="&addressPage.cancelButton.label;"
                     data-back-button-label="&addressPage.backButton.label;"
--- a/browser/components/payments/test/browser/browser_payments_onboarding_wizard.js
+++ b/browser/components/payments/test/browser/browser_payments_onboarding_wizard.js
@@ -1,87 +1,151 @@
 "use strict";
 
-add_task(async function test_onboarding_wizard_without_saved_addresses() {
+async function addAddress() {
+  let onChanged = TestUtils.topicObserved("formautofill-storage-changed",
+                                          (subject, data) => data == "add");
+  formAutofillStorage.addresses.add(PTU.Addresses.TimBL);
+  await onChanged;
+}
+
+add_task(async function test_onboarding_wizard_without_saved_addresses_and_saved_cards() {
   await BrowserTestUtils.withNewTab({
     gBrowser,
     url: BLANK_PAGE_URL,
   }, async browser => {
     cleanupFormAutofillStorage();
 
     info("Opening the payment dialog");
     let {win, frame} =
       await setupPaymentDialog(browser, {
         methodData: [PTU.MethodData.basicCard],
         details: PTU.Details.total60USD,
         options: PTU.Options.requestShippingOption,
         merchantTaskFn: PTU.ContentTasks.createAndShowRequest,
       });
 
     info("Checking if the address page has been rendered");
+    let isAddressPageSaveButtonVisible =
+      await spawnPaymentDialogTask(frame, PTU.DialogContentTasks.isElementVisible,
+                                   "address-form .save-button");
+    ok(isAddressPageSaveButtonVisible, "Address page is rendered");
+
     await spawnPaymentDialogTask(frame, async function() {
       let {
         PaymentTestUtils: PTU,
       } = ChromeUtils.import("resource://testing-common/PaymentTestUtils.jsm", {});
 
-      await ContentTaskUtils.waitForCondition(() =>
-        content.document.getElementById("address-page-cancel-button"), "Address page is rendered");
-
       await PTU.DialogContentUtils.waitForState(content, (state) => {
         return state.page.id == "address-page";
-      }, "Address page is shown first if there are no saved addresses");
+      }, "Address page is shown first during on-boarding if there are no saved addresses");
     });
 
     info("Check if the total header is visible on the address page during on-boarding");
     let isHeaderVisible =
       spawnPaymentDialogTask(frame, PTU.DialogContentTasks.isElementVisible, "header");
     ok(isHeaderVisible, "Total Header is visible on the address page during on-boarding");
+    let headerTextContent =
+      spawnPaymentDialogTask(frame, PTU.DialogContentTasks.getElementTextContent, "header");
+    ok(headerTextContent, "Total Header contains text");
+
+    let isAddressPageCancelButtonVisible =
+      await spawnPaymentDialogTask(frame, PTU.DialogContentTasks.isElementVisible,
+                                   "address-form .cancel-button");
+    ok(isAddressPageCancelButtonVisible, "The cancel button on the address page is visible");
+
+    await spawnPaymentDialogTask(frame, async function() {
+      let {
+        PaymentTestUtils: PTU,
+      } = ChromeUtils.import("resource://testing-common/PaymentTestUtils.jsm", {});
+
+      for (let [key, val] of Object.entries(PTU.Addresses.TimBL2)) {
+        let field = content.document.getElementById(key);
+        if (!field) {
+          ok(false, `${key} field not found`);
+        }
+        field.value = val;
+        ok(!field.disabled, `Field #${key} shouldn't be disabled`);
+      }
+      content.document.querySelector("address-form .save-button").click();
+    });
+
+    let isBasicCardPageSaveButtonVisible =
+      await spawnPaymentDialogTask(frame, PTU.DialogContentTasks.isElementVisible,
+                                   "basic-card-form .save-button");
+    ok(isBasicCardPageSaveButtonVisible, "Basic card page is rendered");
+
+    await spawnPaymentDialogTask(frame, async function() {
+      let {
+        PaymentTestUtils: PTU,
+      } = ChromeUtils.import("resource://testing-common/PaymentTestUtils.jsm", {});
+
+      await PTU.DialogContentUtils.waitForState(content, (state) => {
+        return state.page.id == "basic-card-page";
+      }, "Basic card page is shown after the address page during on boarding");
+
+      for (let [key, val] of Object.entries(PTU.BasicCards.JohnDoe)) {
+        let field = content.document.getElementById(key);
+        field.value = val;
+        ok(!field.disabled, `Field #${key} shouldn't be disabled`);
+      }
+      content.document.querySelector("basic-card-form .save-button").click();
+    });
+
+    let isPaymentSummaryCancelButtonVisible =
+      await spawnPaymentDialogTask(frame, PTU.DialogContentTasks.isElementVisible, "#cancel");
+    ok(isPaymentSummaryCancelButtonVisible,
+       "Payment summary page is shown after the basic card page during on boarding");
 
     info("Closing the payment dialog");
     spawnPaymentDialogTask(frame, async function() {
-      content.document.getElementById("address-page-cancel-button").click();
+      content.document.getElementById("cancel").click();
     });
     await BrowserTestUtils.waitForCondition(() => win.closed, "dialog should be closed");
   });
 });
 
-add_task(async function test_onboarding_wizard_with_saved_addresses() {
+add_task(async function test_onboarding_wizard_with_saved_addresses_and_saved_cards() {
   await BrowserTestUtils.withNewTab({
     gBrowser,
     url: BLANK_PAGE_URL,
   }, async browser => {
     addSampleAddressesAndBasicCard();
 
     info("Opening the payment dialog");
     let {win, frame} =
       await setupPaymentDialog(browser, {
         methodData: [PTU.MethodData.basicCard],
         details: PTU.Details.total60USD,
         options: PTU.Options.requestShippingOption,
         merchantTaskFn: PTU.ContentTasks.createAndShowRequest,
       });
 
     info("Checking if the payment summary page is now visible");
+    let isPaymentSummaryCancelButtonVisible =
+      await spawnPaymentDialogTask(frame, PTU.DialogContentTasks.isElementVisible, "#cancel");
+    ok(isPaymentSummaryCancelButtonVisible, "Payment summary page is rendered");
+
     await spawnPaymentDialogTask(frame, async function() {
       let {
         PaymentTestUtils: PTU,
       } = ChromeUtils.import("resource://testing-common/PaymentTestUtils.jsm", {});
 
-      await ContentTaskUtils.waitForCondition(() => content.document.getElementById("cancel"),
-                                              "Payment summary page is rendered");
-
       await PTU.DialogContentUtils.waitForState(content, (state) => {
         return state.page.id == "payment-summary";
-      }, "Payment summary page is shown when there are saved addresses");
+      }, "Payment summary page is shown first when there are saved addresses and saved cards");
     });
 
     info("Check if the total header is visible on payments summary page");
     let isHeaderVisible =
       spawnPaymentDialogTask(frame, PTU.DialogContentTasks.isElementVisible, "header");
     ok(isHeaderVisible, "Total Header is visible on the payment summary page");
+    let headerTextContent =
+      spawnPaymentDialogTask(frame, PTU.DialogContentTasks.getElementTextContent, "header");
+    ok(headerTextContent, "Total Header contains text");
 
     // Click on the Add/Edit buttons in the payment summary page to check if
     // the total header is visible on the address page and the basic card page.
     let buttons = ["address-picker[selected-state-key='selectedShippingAddress'] .add-link",
       "address-picker[selected-state-key='selectedShippingAddress'] .edit-link",
       "payment-method-picker .add-link",
       "payment-method-picker .edit-link"];
     for (let button of buttons) {
@@ -89,35 +153,29 @@ add_task(async function test_onboarding_
       await spawnPaymentDialogTask(frame, async function(button) {
         let {
           PaymentTestUtils: PTU,
         } = ChromeUtils.import("resource://testing-common/PaymentTestUtils.jsm", {});
 
         content.document.querySelector(button).click();
 
         if (button.startsWith("address")) {
-          await ContentTaskUtils.waitForCondition(() =>
-            content.document.querySelector("address-form .back-button"),
-                                                  "Address page is rendered");
           await PTU.DialogContentUtils.waitForState(content, (state) => {
             return state.page.id == "address-page";
           }, "Address page is shown");
         } else {
-          await ContentTaskUtils.waitForCondition(() =>
-            content.document.querySelector("basic-card-form .back-button"),
-                                                  "Basic card page is rendered");
           await PTU.DialogContentUtils.waitForState(content, (state) => {
             return state.page.id == "basic-card-page";
           }, "Basic card page is shown");
         }
       }, button);
 
       isHeaderVisible =
         await spawnPaymentDialogTask(frame, PTU.DialogContentTasks.isElementVisible, "header");
-      ok(!isHeaderVisible, "Total Header is hidden on the address page");
+      ok(!isHeaderVisible, "Total Header is hidden on the address/basic card page");
 
       if (button.startsWith("address")) {
         spawnPaymentDialogTask(frame, async function() {
           content.document.querySelector("address-form .back-button").click();
         });
       } else {
         spawnPaymentDialogTask(frame, async function() {
           content.document.querySelector("basic-card-form .back-button").click();
@@ -129,8 +187,62 @@ add_task(async function test_onboarding_
     spawnPaymentDialogTask(frame, async function() {
       content.document.getElementById("cancel").click();
     });
     await BrowserTestUtils.waitForCondition(() => win.closed, "dialog should be closed");
 
     cleanupFormAutofillStorage();
   });
 });
+
+add_task(async function test_onboarding_wizard_with_saved_addresses_and_no_saved_cards() {
+  await BrowserTestUtils.withNewTab({
+    gBrowser,
+    url: BLANK_PAGE_URL,
+  }, async browser => {
+    addAddress();
+
+    info("Opening the payment dialog");
+    let {win, frame} =
+      await setupPaymentDialog(browser, {
+        methodData: [PTU.MethodData.basicCard],
+        details: PTU.Details.total60USD,
+        options: PTU.Options.requestShippingOption,
+        merchantTaskFn: PTU.ContentTasks.createAndShowRequest,
+      });
+
+    info("Checking if the basic card has been rendered");
+    let isBasicCardPageSaveButtonVisible =
+      await spawnPaymentDialogTask(frame, PTU.DialogContentTasks.isElementVisible,
+                                   "basic-card-form .save-button");
+    ok(isBasicCardPageSaveButtonVisible, "Basic card page is rendered");
+
+    await spawnPaymentDialogTask(frame, async function() {
+      let {
+        PaymentTestUtils: PTU,
+      } = ChromeUtils.import("resource://testing-common/PaymentTestUtils.jsm", {});
+
+      await PTU.DialogContentUtils.waitForState(content, (state) => {
+        return state.page.id == "basic-card-page";
+      }, "Basic card page is shown first if there are saved addresses during on boarding");
+    });
+
+    info("Check if the total header is visible on the basic card page during on-boarding");
+    let isHeaderVisible =
+      spawnPaymentDialogTask(frame, PTU.DialogContentTasks.isElementVisible, "header");
+    ok(isHeaderVisible, "Total Header is visible on the basic card page during on-boarding");
+    let headerTextContent =
+      spawnPaymentDialogTask(frame, PTU.DialogContentTasks.getElementTextContent, "header");
+    ok(headerTextContent, "Total Header contains text");
+
+    let isBasicCardPageCancelButtonVisible =
+      await spawnPaymentDialogTask(frame, PTU.DialogContentTasks.isElementVisible,
+                                   "basic-card-form .cancel-button");
+    ok(isBasicCardPageCancelButtonVisible, "Cancel button is visible on the basic card page");
+
+    spawnPaymentDialogTask(frame, async function() {
+      content.document.querySelector("basic-card-form .cancel-button").click();
+    });
+    await BrowserTestUtils.waitForCondition(() => win.closed, "dialog should be closed");
+
+    cleanupFormAutofillStorage();
+  });
+});