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