--- 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();
+});