Bug 1427958 - Check modified dates when determining if the shipping address has changed. r?MattN draft
authorSam Foster <sfoster@mozilla.com>
Wed, 28 Mar 2018 15:54:20 -0700
changeset 778705 c8d9dbd9e2ee32bad6d4f63efee8ef579ecbe547
parent 778675 0d661c592a164ca918ed12f87cbcf7f52c293359
push id105578
push userbmo:sfoster@mozilla.com
push dateFri, 06 Apr 2018 20:48:29 +0000
reviewersMattN
bugs1427958
milestone61.0a1
Bug 1427958 - Check modified dates when determining if the shipping address has changed. r?MattN * Compare address timeLastModified as well as the selectedShippingAddress guid when handling new state * Add tests to ensure editing or removing the selected address record correctly updates the UI MozReview-Commit-ID: InQDP55Fs2
toolkit/components/payments/res/containers/payment-dialog.js
toolkit/components/payments/test/PaymentTestUtils.jsm
toolkit/components/payments/test/browser/browser_change_shipping.js
toolkit/components/payments/test/browser/head.js
--- a/toolkit/components/payments/res/containers/payment-dialog.js
+++ b/toolkit/components/payments/res/containers/payment-dialog.js
@@ -101,33 +101,47 @@ class PaymentDialog extends PaymentState
   /**
    * Set some state from the privileged parent process.
    * Other elements that need to set state should use their own `this.requestStore.setState`
    * method provided by the `PaymentStateSubscriberMixin`.
    *
    * @param {object} state - See `PaymentsStore.setState`
    */
   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 {
       savedAddresses,
       savedBasicCards,
       selectedPayerAddress,
       selectedPaymentCard,
       selectedShippingAddress,
       selectedShippingOption,
     } = state;
     let shippingOptions = state.request.paymentDetails.shippingOptions;
+    let shippingAddress = selectedShippingAddress && savedAddresses[selectedShippingAddress];
+    let oldShippingAddress = selectedShippingAddress &&
+                             oldSavedAddresses[selectedShippingAddress];
 
     // Ensure `selectedShippingAddress` never refers to a deleted address and refers
-    // to an address if one exists.
-    if (!savedAddresses[selectedShippingAddress]) {
+    // to an address if one exists. We also compare address timestamps to handle changes
+    // made outside the payments UI.
+    if (shippingAddress) {
+      // invalidate the cached value if the address was modified
+      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
       this.requestStore.setState({
         selectedShippingAddress: Object.keys(savedAddresses)[0] || null,
       });
     }
 
     // Ensure `selectedPaymentCard` never refers to a deleted payment card and refers
     // to a payment card if one exists.
     if (!savedBasicCards[selectedPaymentCard]) {
@@ -152,17 +166,16 @@ class PaymentDialog extends PaymentState
         selectedShippingOption = shippingOptions[0].id;
       }
       this._cachedState.selectedShippingOption = selectedShippingOption;
       this.requestStore.setState({
         selectedShippingOption,
       });
     }
 
-
     // Ensure `selectedPayerAddress` never refers to a deleted address and refers
     // to an address if one exists.
     if (!savedAddresses[selectedPayerAddress]) {
       this.requestStore.setState({
         selectedPayerAddress: Object.keys(savedAddresses)[0] || null,
       });
     }
   }
--- a/toolkit/components/payments/test/PaymentTestUtils.jsm
+++ b/toolkit/components/payments/test/PaymentTestUtils.jsm
@@ -119,16 +119,36 @@ var PaymentTestUtils = {
         selectedOptionIndex,
         selectedOptionValue: selectedOption.getAttribute("value"),
         selectedOptionLabel: selectedOption.getAttribute("label"),
         selectedOptionCurrency: currencyAmount.getAttribute("currency"),
         selectedOptionAmount: currencyAmount.getAttribute("amount"),
       };
     },
 
+    getShippingAddresses: () => {
+      let doc = content.document;
+      let addressPicker =
+        doc.querySelector("address-picker[selected-state-key='selectedShippingAddress']");
+      let select = addressPicker.querySelector("rich-select");
+      let popupBox = Cu.waiveXrays(select).popupBox;
+      let options = Array.from(popupBox.children).map(option => {
+        return {
+          guid: option.guid,
+          country: option.country,
+          selected: option.selected,
+        };
+      });
+      let selectedOptionIndex = options.findIndex(item => item.selected);
+      return {
+        selectedOptionIndex,
+        options,
+      };
+    },
+
     selectShippingAddressByCountry: country => {
       let doc = content.document;
       let addressPicker =
         doc.querySelector("address-picker[selected-state-key='selectedShippingAddress']");
       let select = addressPicker.querySelector("rich-select");
       let option = select.querySelector(`[country="${country}"]`);
       select.click();
       option.click();
--- a/toolkit/components/payments/test/browser/browser_change_shipping.js
+++ b/toolkit/components/payments/test/browser/browser_change_shipping.js
@@ -1,29 +1,17 @@
 "use strict";
 
-add_task(async function setup_profiles() {
-  let onChanged = TestUtils.topicObserved("formautofill-storage-changed",
-                                          (subject, data) => data == "add");
-  formAutofillStorage.addresses.add(PTU.Addresses.TimBL);
-  await onChanged;
-
-  onChanged = TestUtils.topicObserved("formautofill-storage-changed",
-                                      (subject, data) => data == "add");
-  formAutofillStorage.addresses.add(PTU.Addresses.TimBL2);
-  await onChanged;
-
-  onChanged = TestUtils.topicObserved("formautofill-storage-changed",
-                                      (subject, data) => data == "add");
-
-  formAutofillStorage.creditCards.add(PTU.BasicCards.JohnDoe);
-  await onChanged;
-});
+async function setup() {
+  await setupFormAutofillStorage();
+  await addSampleAddressesAndBasicCard();
+}
 
 add_task(async function test_change_shipping() {
+  await setup();
   await BrowserTestUtils.withNewTab({
     gBrowser,
     url: BLANK_PAGE_URL,
   }, async browser => {
     let {win, frame} =
       await setupPaymentDialog(browser, {
         methodData: [PTU.MethodData.basicCard],
         details: PTU.Details.twoShippingOptions,
@@ -94,19 +82,21 @@ add_task(async function test_change_ship
     let methodDetails = result.methodDetails;
     is(methodDetails.cardholderName, "John Doe", "Check cardholderName");
     is(methodDetails.cardNumber, "999999999999", "Check cardNumber");
     is(methodDetails.expiryMonth, "01", "Check expiryMonth");
     is(methodDetails.expiryYear, "9999", "Check expiryYear");
 
     await BrowserTestUtils.waitForCondition(() => win.closed, "dialog should be closed");
   });
+  await cleanupFormAutofillStorage();
 });
 
 add_task(async function test_no_shippingchange_without_shipping() {
+  await setup();
   await BrowserTestUtils.withNewTab({
     gBrowser,
     url: BLANK_PAGE_URL,
   }, async browser => {
     let {win, frame} =
       await setupPaymentDialog(browser, {
         methodData: [PTU.MethodData.basicCard],
         details: PTU.Details.twoShippingOptions,
@@ -134,9 +124,108 @@ add_task(async function test_no_shipping
     let methodDetails = result.methodDetails;
     is(methodDetails.cardholderName, "John Doe", "Check cardholderName");
     is(methodDetails.cardNumber, "999999999999", "Check cardNumber");
     is(methodDetails.expiryMonth, "01", "Check expiryMonth");
     is(methodDetails.expiryYear, "9999", "Check expiryYear");
 
     await BrowserTestUtils.waitForCondition(() => win.closed, "dialog should be closed");
   });
+  await cleanupFormAutofillStorage();
 });
+
+add_task(async function test_address_edit() {
+  await setup();
+  await BrowserTestUtils.withNewTab({
+    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,
+      }
+    );
+
+    let addressOptions =
+      await spawnPaymentDialogTask(frame, PTU.DialogContentTasks.getShippingAddresses);
+    info("initial addressOptions: " + JSON.stringify(addressOptions));
+    let selectedIndex = addressOptions.selectedOptionIndex;
+    let selectedAddressGuid = addressOptions.options[selectedIndex].guid;
+    let selectedAddress = formAutofillStorage.addresses.get(selectedAddressGuid);
+
+    is(selectedIndex, 0, "First address should be selected");
+    ok(selectedAddress, "Selected address does exist in the address collection");
+    is(selectedAddress.country, "US", "Expected initial country value");
+
+    info("Updating the address directly in the store");
+    await formAutofillStorage.addresses.update(selectedAddress.guid, {
+      country: "CA",
+    }, true);
+
+    await ContentTask.spawn(browser, {
+      eventName: "shippingaddresschange",
+    }, PTU.ContentTasks.promisePaymentRequestEvent);
+
+    addressOptions =
+      await spawnPaymentDialogTask(frame, PTU.DialogContentTasks.getShippingAddresses);
+    info("updated addressOptions: " + JSON.stringify(addressOptions));
+    selectedIndex = addressOptions.selectedOptionIndex;
+    let newSelectedAddressGuid = addressOptions.options[selectedIndex].guid;
+
+    is(newSelectedAddressGuid, selectedAddressGuid, "Selected guid hasnt changed");
+    selectedAddress = formAutofillStorage.addresses.get(selectedAddressGuid);
+
+    is(selectedIndex, 0, "First address should be selected");
+    is(selectedAddress.country, "CA", "Expected changed country value");
+
+    spawnPaymentDialogTask(frame, PTU.DialogContentTasks.manuallyClickCancel);
+    await BrowserTestUtils.waitForCondition(() => win.closed, "dialog should be closed");
+  });
+
+  await cleanupFormAutofillStorage();
+});
+
+add_task(async function test_address_removal() {
+  await setup();
+  await BrowserTestUtils.withNewTab({
+    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,
+      }
+    );
+
+    let addressOptions =
+      await spawnPaymentDialogTask(frame, PTU.DialogContentTasks.getShippingAddresses);
+    info("initial addressOptions: " + JSON.stringify(addressOptions));
+    let selectedIndex = addressOptions.selectedOptionIndex;
+    let selectedAddressGuid = addressOptions.options[selectedIndex].guid;
+
+    is(selectedIndex, 0, "First address should be selected");
+    is(addressOptions.options.length, 2, "Should be 2 address options initially");
+
+    info("Remove the selected address from the store");
+    await formAutofillStorage.addresses.remove(selectedAddressGuid);
+
+    await ContentTask.spawn(browser, {
+      eventName: "shippingaddresschange",
+    }, PTU.ContentTasks.promisePaymentRequestEvent);
+
+    addressOptions =
+      await spawnPaymentDialogTask(frame, PTU.DialogContentTasks.getShippingAddresses);
+    info("updated addressOptions: " + JSON.stringify(addressOptions));
+    selectedIndex = addressOptions.selectedOptionIndex;
+
+    is(selectedIndex, 0, "First address should be selected");
+    is(addressOptions.options.length, 1, "Should now be 1 address option");
+
+    spawnPaymentDialogTask(frame, PTU.DialogContentTasks.manuallyClickCancel);
+    await BrowserTestUtils.waitForCondition(() => win.closed, "dialog should be closed");
+  });
+
+  await cleanupFormAutofillStorage();
+});
--- a/toolkit/components/payments/test/browser/head.js
+++ b/toolkit/components/payments/test/browser/head.js
@@ -211,16 +211,24 @@ async function spawnInDialogForMerchantT
     is(requests.length, 1, "Should have one payment request");
     let request = requests[0];
     ok(!!request.requestId, "Got a payment request with an ID");
 
     await spawnTaskInNewDialog(request.requestId, dialogTaskFn, taskArgs);
   });
 }
 
+async function setupFormAutofillStorage() {
+  await formAutofillStorage.initialize();
+}
+
+function cleanupFormAutofillStorage() {
+  formAutofillStorage.addresses._nukeAllRecords();
+  formAutofillStorage.creditCards._nukeAllRecords();
+}
+
 add_task(async function setup_head() {
-  await formAutofillStorage.initialize();
+  await setupFormAutofillStorage();
   registerCleanupFunction(function cleanup() {
     paymentSrv.cleanup();
-    formAutofillStorage.addresses._nukeAllRecords();
-    formAutofillStorage.creditCards._nukeAllRecords();
+    cleanupFormAutofillStorage();
   });
 });