зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1482689 - Use AddressPicker for the card billing address UI. r=MattN
* New BillingAddressPicker subclass of AddressPicker which just overrides some of the state-related behavior that * Allow the RichSelect's popupBox (<select>) to be assigned after the constructor * A couple new mochitests for the new/different behavior * Update the test to expect 'edit' to be hidden when the empty option is selected Differential Revision: https://phabricator.services.mozilla.com/D9321 --HG-- extra : moz-landing-system : lando
This commit is contained in:
Родитель
04e84ad672
Коммит
aa95912b8b
|
@ -24,10 +24,12 @@ export default class RichSelect extends ObservedPropertiesMixin(HTMLElement) {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.popupBox = document.createElement("select");
|
this.popupBox = document.createElement("select");
|
||||||
this.popupBox.addEventListener("change", this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
|
// the popupBox element may change in between constructor and being connected
|
||||||
|
// so wait until connected before listening to events on it
|
||||||
|
this.popupBox.addEventListener("change", this);
|
||||||
this.appendChild(this.popupBox);
|
this.appendChild(this.popupBox);
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
@ -74,7 +76,7 @@ export default class RichSelect extends ObservedPropertiesMixin(HTMLElement) {
|
||||||
|
|
||||||
if (this.value) {
|
if (this.value) {
|
||||||
let optionType = this.getAttribute("option-type");
|
let optionType = this.getAttribute("option-type");
|
||||||
if (selectedRichOption.localName != optionType) {
|
if (!selectedRichOption || selectedRichOption.localName != optionType) {
|
||||||
selectedRichOption = document.createElement(optionType);
|
selectedRichOption = document.createElement(optionType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,10 @@ export default class AddressPicker extends RichPicker {
|
||||||
|
|
||||||
attributeChangedCallback(name, oldValue, newValue) {
|
attributeChangedCallback(name, oldValue, newValue) {
|
||||||
super.attributeChangedCallback(name, oldValue, newValue);
|
super.attributeChangedCallback(name, oldValue, newValue);
|
||||||
if (AddressPicker.pickerAttributes.includes(name) && oldValue !== newValue) {
|
// connectedCallback may add and adjust elements & values
|
||||||
|
// so avoid calling render before the element is connected
|
||||||
|
if (this.isConnected &&
|
||||||
|
AddressPicker.pickerAttributes.includes(name) && oldValue !== newValue) {
|
||||||
this.render(this.requestStore.getState());
|
this.render(this.requestStore.getState());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -89,7 +92,26 @@ export default class AddressPicker extends RichPicker {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get options() {
|
||||||
|
return this.dropdown.popupBox.options;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {object} state - See `PaymentsStore.setState`
|
||||||
|
* The value of the picker is retrieved from state store rather than the DOM
|
||||||
|
* @returns {string} guid
|
||||||
|
*/
|
||||||
|
getCurrentValue(state) {
|
||||||
|
let [selectedKey, selectedLeaf] = this.selectedStateKey.split("|");
|
||||||
|
let guid = state[selectedKey];
|
||||||
|
if (selectedLeaf) {
|
||||||
|
guid = guid[selectedLeaf];
|
||||||
|
}
|
||||||
|
return guid;
|
||||||
|
}
|
||||||
|
|
||||||
render(state) {
|
render(state) {
|
||||||
|
let selectedAddressGUID = this.getCurrentValue(state) || "";
|
||||||
let addresses = paymentRequest.getAddresses(state);
|
let addresses = paymentRequest.getAddresses(state);
|
||||||
let desiredOptions = [];
|
let desiredOptions = [];
|
||||||
let filteredAddresses = this.filterAddresses(addresses, this.fieldNames);
|
let filteredAddresses = this.filterAddresses(addresses, this.fieldNames);
|
||||||
|
@ -131,12 +153,18 @@ export default class AddressPicker extends RichPicker {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.dropdown.popupBox.textContent = "";
|
this.dropdown.popupBox.textContent = "";
|
||||||
|
|
||||||
|
if (this._allowEmptyOption) {
|
||||||
|
let optionEl = document.createElement("option");
|
||||||
|
optionEl.value = "";
|
||||||
|
desiredOptions.unshift(optionEl);
|
||||||
|
}
|
||||||
|
|
||||||
for (let option of desiredOptions) {
|
for (let option of desiredOptions) {
|
||||||
this.dropdown.popupBox.appendChild(option);
|
this.dropdown.popupBox.appendChild(option);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update selectedness after the options are updated
|
// Update selectedness after the options are updated
|
||||||
let selectedAddressGUID = state[this.selectedStateKey];
|
|
||||||
this.dropdown.value = selectedAddressGUID;
|
this.dropdown.value = selectedAddressGUID;
|
||||||
|
|
||||||
if (selectedAddressGUID && selectedAddressGUID !== this.dropdown.value) {
|
if (selectedAddressGUID && selectedAddressGUID !== this.dropdown.value) {
|
||||||
|
@ -161,8 +189,8 @@ export default class AddressPicker extends RichPicker {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
let merchantFieldErrors = AddressForm.merchantFieldErrorsForForm(state,
|
let merchantFieldErrors = AddressForm.merchantFieldErrorsForForm(
|
||||||
[this.selectedStateKey]);
|
state, this.selectedStateKey.split("|"));
|
||||||
// TODO: errors in priority order.
|
// TODO: errors in priority order.
|
||||||
return Object.values(merchantFieldErrors).find(msg => {
|
return Object.values(merchantFieldErrors).find(msg => {
|
||||||
return typeof(msg) == "string" && msg.length;
|
return typeof(msg) == "string" && msg.length;
|
||||||
|
@ -182,12 +210,23 @@ export default class AddressPicker extends RichPicker {
|
||||||
}
|
}
|
||||||
|
|
||||||
onChange(event) {
|
onChange(event) {
|
||||||
let selectedKey = this.selectedStateKey;
|
let [selectedKey, selectedLeaf] = this.selectedStateKey.split("|");
|
||||||
if (selectedKey) {
|
if (!selectedKey) {
|
||||||
this.requestStore.setState({
|
return;
|
||||||
[selectedKey]: this.dropdown.value,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
// selectedStateKey can be a '|' delimited string indicating a path into the state object
|
||||||
|
// to update with the new value
|
||||||
|
let newState = {};
|
||||||
|
|
||||||
|
if (selectedLeaf) {
|
||||||
|
let currentState = this.requestStore.getState();
|
||||||
|
newState[selectedKey] = Object.assign({},
|
||||||
|
currentState[selectedKey],
|
||||||
|
{ [selectedLeaf]: this.dropdown.value });
|
||||||
|
} else {
|
||||||
|
newState[selectedKey] = this.dropdown.value;
|
||||||
|
}
|
||||||
|
this.requestStore.setState(newState);
|
||||||
}
|
}
|
||||||
|
|
||||||
onClick({target}) {
|
onClick({target}) {
|
||||||
|
@ -197,7 +236,7 @@ export default class AddressPicker extends RichPicker {
|
||||||
},
|
},
|
||||||
"address-page": {
|
"address-page": {
|
||||||
addressFields: this.getAttribute("address-fields"),
|
addressFields: this.getAttribute("address-fields"),
|
||||||
selectedStateKey: [this.selectedStateKey],
|
selectedStateKey: this.selectedStateKey.split("|"),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -208,9 +247,8 @@ export default class AddressPicker extends RichPicker {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case this.editLink: {
|
case this.editLink: {
|
||||||
let state = this.requestStore.getState();
|
let currentState = this.requestStore.getState();
|
||||||
let selectedAddressGUID = state[this.selectedStateKey];
|
nextState["address-page"].guid = this.getCurrentValue(currentState);
|
||||||
nextState["address-page"].guid = selectedAddressGUID;
|
|
||||||
nextState["address-page"].title = this.dataset.editAddressTitle;
|
nextState["address-page"].title = this.dataset.editAddressTitle;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,11 +31,6 @@ basic-card-form .editCreditCardForm .persist-checkbox {
|
||||||
display: grid;
|
display: grid;
|
||||||
}
|
}
|
||||||
|
|
||||||
#billingAddressGUID {
|
|
||||||
/* XXX: temporary until converted to a rich-picker in bug 1482689 */
|
|
||||||
margin: 14px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
basic-card-form > footer > .cancel-button {
|
basic-card-form > footer > .cancel-button {
|
||||||
/* When cancel is shown (during onboarding), it should always be on the left with a space after it */
|
/* When cancel is shown (during onboarding), it should always be on the left with a space after it */
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
/* import-globals-from ../../../../../browser/extensions/formautofill/content/autofillEditForms.js*/
|
/* import-globals-from ../../../../../browser/extensions/formautofill/content/autofillEditForms.js*/
|
||||||
import AcceptedCards from "../components/accepted-cards.js";
|
import AcceptedCards from "../components/accepted-cards.js";
|
||||||
|
import BillingAddressPicker from "./billing-address-picker.js";
|
||||||
import CscInput from "../components/csc-input.js";
|
import CscInput from "../components/csc-input.js";
|
||||||
import LabelledCheckbox from "../components/labelled-checkbox.js";
|
import LabelledCheckbox from "../components/labelled-checkbox.js";
|
||||||
import PaymentRequestPage from "../components/payment-request-page.js";
|
import PaymentRequestPage from "../components/payment-request-page.js";
|
||||||
|
@ -27,15 +28,6 @@ export default class BasicCardForm extends PaymentStateSubscriberMixin(PaymentRe
|
||||||
this.genericErrorText.setAttribute("aria-live", "polite");
|
this.genericErrorText.setAttribute("aria-live", "polite");
|
||||||
this.genericErrorText.classList.add("page-error");
|
this.genericErrorText.classList.add("page-error");
|
||||||
|
|
||||||
this.addressAddLink = document.createElement("a");
|
|
||||||
this.addressAddLink.className = "add-link";
|
|
||||||
this.addressAddLink.href = "javascript:void(0)";
|
|
||||||
this.addressAddLink.addEventListener("click", this);
|
|
||||||
this.addressEditLink = document.createElement("a");
|
|
||||||
this.addressEditLink.className = "edit-link";
|
|
||||||
this.addressEditLink.href = "javascript:void(0)";
|
|
||||||
this.addressEditLink.addEventListener("click", this);
|
|
||||||
|
|
||||||
this.cscInput = new CscInput({
|
this.cscInput = new CscInput({
|
||||||
useAlwaysVisiblePlaceholder: true,
|
useAlwaysVisiblePlaceholder: true,
|
||||||
inputId: "cc-csc",
|
inputId: "cc-csc",
|
||||||
|
@ -85,6 +77,38 @@ export default class BasicCardForm extends PaymentStateSubscriberMixin(PaymentRe
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_upgradeBillingAddressPicker() {
|
||||||
|
let addressRow = this.form.querySelector(".billingAddressRow");
|
||||||
|
let addressPicker = this.billingAddressPicker = new BillingAddressPicker();
|
||||||
|
|
||||||
|
// Wrap the existing <select> that the formHandler manages
|
||||||
|
if (addressPicker.dropdown.popupBox) {
|
||||||
|
addressPicker.dropdown.popupBox.remove();
|
||||||
|
}
|
||||||
|
addressPicker.dropdown.popupBox = this.form.querySelector("#billingAddressGUID");
|
||||||
|
|
||||||
|
// Hide the original label as the address picker provide its own,
|
||||||
|
// but we'll copy the localized textContent from it when rendering
|
||||||
|
addressRow.querySelector(".label-text").hidden = true;
|
||||||
|
|
||||||
|
addressPicker.dataset.addLinkLabel = this.dataset.addressAddLinkLabel;
|
||||||
|
addressPicker.dataset.editLinkLabel = this.dataset.addressEditLinkLabel;
|
||||||
|
addressPicker.dataset.fieldSeparator = this.dataset.addressFieldSeparator;
|
||||||
|
addressPicker.dataset.addAddressTitle = this.dataset.billingAddressTitleAdd;
|
||||||
|
addressPicker.dataset.editAddressTitle = this.dataset.billingAddressTitleEdit;
|
||||||
|
addressPicker.dataset.invalidLabel = this.dataset.invalidAddressLabel;
|
||||||
|
// break-after-nth-field, address-fields not needed here
|
||||||
|
|
||||||
|
// this state is only used to carry the selected guid between pages;
|
||||||
|
// the select#billingAddressGUID is the source of truth for the current value
|
||||||
|
addressPicker.setAttribute("selected-state-key", "basic-card-page|billingAddressGUID");
|
||||||
|
|
||||||
|
addressPicker.addLink.addEventListener("click", this);
|
||||||
|
addressPicker.editLink.addEventListener("click", this);
|
||||||
|
|
||||||
|
addressRow.appendChild(addressPicker);
|
||||||
|
}
|
||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
this.promiseReady.then(form => {
|
this.promiseReady.then(form => {
|
||||||
this.body.appendChild(form);
|
this.body.appendChild(form);
|
||||||
|
@ -106,6 +130,8 @@ export default class BasicCardForm extends PaymentStateSubscriberMixin(PaymentRe
|
||||||
form.addEventListener("input", this);
|
form.addEventListener("input", this);
|
||||||
form.addEventListener("invalid", this);
|
form.addEventListener("invalid", this);
|
||||||
|
|
||||||
|
this._upgradeBillingAddressPicker();
|
||||||
|
|
||||||
// The "invalid" event does not bubble and needs to be listened for on each
|
// The "invalid" event does not bubble and needs to be listened for on each
|
||||||
// form element.
|
// form element.
|
||||||
for (let field of this.form.elements) {
|
for (let field of this.form.elements) {
|
||||||
|
@ -117,18 +143,7 @@ export default class BasicCardForm extends PaymentStateSubscriberMixin(PaymentRe
|
||||||
cscContainer.textContent = "";
|
cscContainer.textContent = "";
|
||||||
cscContainer.appendChild(this.cscInput);
|
cscContainer.appendChild(this.cscInput);
|
||||||
|
|
||||||
let fragment = document.createDocumentFragment();
|
|
||||||
fragment.append(" ");
|
|
||||||
fragment.append(this.addressEditLink);
|
|
||||||
fragment.append(this.addressAddLink);
|
|
||||||
let billingAddressRow = this.form.querySelector(".billingAddressRow");
|
let billingAddressRow = this.form.querySelector(".billingAddressRow");
|
||||||
|
|
||||||
// XXX: Bug 1482689 - Remove the label-text class from the billing field
|
|
||||||
// which will be removed when switching to <rich-select>.
|
|
||||||
billingAddressRow.querySelector(".label-text").classList.remove("label-text");
|
|
||||||
|
|
||||||
billingAddressRow.appendChild(fragment);
|
|
||||||
|
|
||||||
form.insertBefore(this.persistCheckbox, billingAddressRow);
|
form.insertBefore(this.persistCheckbox, billingAddressRow);
|
||||||
form.insertBefore(this.acceptedCardsList, billingAddressRow);
|
form.insertBefore(this.acceptedCardsList, billingAddressRow);
|
||||||
this.body.appendChild(this.genericErrorText);
|
this.body.appendChild(this.genericErrorText);
|
||||||
|
@ -167,10 +182,13 @@ export default class BasicCardForm extends PaymentStateSubscriberMixin(PaymentRe
|
||||||
this.cscInput.frontTooltip = this.dataset.cscFrontInfoTooltip;
|
this.cscInput.frontTooltip = this.dataset.cscFrontInfoTooltip;
|
||||||
this.cscInput.backTooltip = this.dataset.cscBackInfoTooltip;
|
this.cscInput.backTooltip = this.dataset.cscBackInfoTooltip;
|
||||||
|
|
||||||
|
// The label text from the form isn't available until render() time.
|
||||||
|
let labelText = this.form.querySelector(".billingAddressRow .label-text").textContent;
|
||||||
|
this.billingAddressPicker.setAttribute("label", labelText);
|
||||||
|
|
||||||
this.persistCheckbox.label = this.dataset.persistCheckboxLabel;
|
this.persistCheckbox.label = this.dataset.persistCheckboxLabel;
|
||||||
this.persistCheckbox.infoTooltip = this.dataset.persistCheckboxInfoTooltip;
|
this.persistCheckbox.infoTooltip = this.dataset.persistCheckboxInfoTooltip;
|
||||||
this.addressAddLink.textContent = this.dataset.addressAddLinkLabel;
|
|
||||||
this.addressEditLink.textContent = this.dataset.addressEditLinkLabel;
|
|
||||||
this.acceptedCardsList.label = this.dataset.acceptedCardsLabel;
|
this.acceptedCardsList.label = this.dataset.acceptedCardsLabel;
|
||||||
|
|
||||||
// The next line needs an onboarding check since we don't set previousId
|
// The next line needs an onboarding check since we don't set previousId
|
||||||
|
@ -227,7 +245,7 @@ export default class BasicCardForm extends PaymentStateSubscriberMixin(PaymentRe
|
||||||
|
|
||||||
this.form.querySelector(".billingAddressRow").hidden = false;
|
this.form.querySelector(".billingAddressRow").hidden = false;
|
||||||
|
|
||||||
let billingAddressSelect = this.form.querySelector("#billingAddressGUID");
|
let billingAddressSelect = this.billingAddressPicker.dropdown;
|
||||||
if (basicCardPage.billingAddressGUID) {
|
if (basicCardPage.billingAddressGUID) {
|
||||||
billingAddressSelect.value = basicCardPage.billingAddressGUID;
|
billingAddressSelect.value = basicCardPage.billingAddressGUID;
|
||||||
} else if (!editing) {
|
} else if (!editing) {
|
||||||
|
@ -244,7 +262,7 @@ export default class BasicCardForm extends PaymentStateSubscriberMixin(PaymentRe
|
||||||
}
|
}
|
||||||
// Need to recalculate the populated state since
|
// Need to recalculate the populated state since
|
||||||
// billingAddressSelect is updated after loadRecord.
|
// billingAddressSelect is updated after loadRecord.
|
||||||
this.formHandler.updatePopulatedState(billingAddressSelect);
|
this.formHandler.updatePopulatedState(billingAddressSelect.popupBox);
|
||||||
|
|
||||||
this.updateRequiredState();
|
this.updateRequiredState();
|
||||||
this.updateSaveButtonState();
|
this.updateSaveButtonState();
|
||||||
|
@ -289,21 +307,18 @@ export default class BasicCardForm extends PaymentStateSubscriberMixin(PaymentRe
|
||||||
paymentRequest.cancel();
|
paymentRequest.cancel();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case this.addressAddLink:
|
case this.billingAddressPicker.addLink:
|
||||||
case this.addressEditLink: {
|
case this.billingAddressPicker.editLink: {
|
||||||
|
// The address-picker has set state for the page to advance to, now set up the
|
||||||
|
// necessary state for returning to and re-rendering this page
|
||||||
let {
|
let {
|
||||||
"basic-card-page": basicCardPage,
|
"basic-card-page": basicCardPage,
|
||||||
|
page,
|
||||||
} = this.requestStore.getState();
|
} = this.requestStore.getState();
|
||||||
let nextState = {
|
let nextState = {
|
||||||
page: {
|
page: Object.assign({}, page, {
|
||||||
id: "address-page",
|
|
||||||
previousId: "basic-card-page",
|
previousId: "basic-card-page",
|
||||||
},
|
}),
|
||||||
"address-page": {
|
|
||||||
guid: null,
|
|
||||||
selectedStateKey: ["basic-card-page", "billingAddressGUID"],
|
|
||||||
title: this.dataset.billingAddressTitleAdd,
|
|
||||||
},
|
|
||||||
"basic-card-page": {
|
"basic-card-page": {
|
||||||
preserveFieldValues: true,
|
preserveFieldValues: true,
|
||||||
guid: basicCardPage.guid,
|
guid: basicCardPage.guid,
|
||||||
|
@ -311,24 +326,18 @@ export default class BasicCardForm extends PaymentStateSubscriberMixin(PaymentRe
|
||||||
selectedStateKey: basicCardPage.selectedStateKey,
|
selectedStateKey: basicCardPage.selectedStateKey,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
let billingAddressGUID = this.form.querySelector("#billingAddressGUID");
|
|
||||||
let selectedOption = billingAddressGUID.selectedOptions.length &&
|
|
||||||
billingAddressGUID.selectedOptions[0];
|
|
||||||
if (evt.target == this.addressEditLink && selectedOption && selectedOption.value) {
|
|
||||||
nextState["address-page"].title = this.dataset.billingAddressTitleEdit;
|
|
||||||
nextState["address-page"].guid = selectedOption.value;
|
|
||||||
}
|
|
||||||
this.requestStore.setState(nextState);
|
this.requestStore.setState(nextState);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case this.backButton: {
|
case this.backButton: {
|
||||||
|
let currentState = this.requestStore.getState();
|
||||||
let {
|
let {
|
||||||
page,
|
page,
|
||||||
request,
|
request,
|
||||||
"address-page": addressPage,
|
"address-page": addressPage,
|
||||||
"basic-card-page": basicCardPage,
|
"basic-card-page": basicCardPage,
|
||||||
selectedShippingAddress,
|
selectedShippingAddress,
|
||||||
} = this.requestStore.getState();
|
} = currentState;
|
||||||
|
|
||||||
let nextState = {
|
let nextState = {
|
||||||
page: {
|
page: {
|
||||||
|
@ -391,7 +400,10 @@ export default class BasicCardForm extends PaymentStateSubscriberMixin(PaymentRe
|
||||||
}
|
}
|
||||||
|
|
||||||
updateSaveButtonState() {
|
updateSaveButtonState() {
|
||||||
this.saveButton.disabled = !this.form.checkValidity();
|
const INVALID_CLASS_NAME = "invalid-selected-option";
|
||||||
|
let isValid = this.form.checkValidity() &&
|
||||||
|
!this.billingAddressPicker.classList.contains(INVALID_CLASS_NAME);
|
||||||
|
this.saveButton.disabled = !isValid;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateRequiredState() {
|
updateRequiredState() {
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
import AddressPicker from "./address-picker.js";
|
||||||
|
/* import-globals-from ../unprivileged-fallbacks.js */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <billing-address-picker></billing-address-picker>
|
||||||
|
* Extends AddressPicker to treat the <select>'s value as the source of truth
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default class BillingAddressPicker extends AddressPicker {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this._allowEmptyOption = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {object?} state - See `PaymentsStore.setState`
|
||||||
|
* The value of the picker is the child dropdown element's value
|
||||||
|
* @returns {string} guid
|
||||||
|
*/
|
||||||
|
getCurrentValue() {
|
||||||
|
return this.dropdown.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
onChange(event) {
|
||||||
|
this.render(this.requestStore.getState());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define("billing-address-picker", BillingAddressPicker);
|
|
@ -16,29 +16,30 @@ export default class RichPicker extends PaymentStateSubscriberMixin(HTMLElement)
|
||||||
|
|
||||||
this.dropdown = new RichSelect();
|
this.dropdown = new RichSelect();
|
||||||
this.dropdown.addEventListener("change", this);
|
this.dropdown.addEventListener("change", this);
|
||||||
this.dropdown.popupBox.id = "select-" + Math.floor(Math.random() * 1000000);
|
|
||||||
|
|
||||||
this.labelElement = document.createElement("label");
|
this.labelElement = document.createElement("label");
|
||||||
this.labelElement.setAttribute("for", this.dropdown.popupBox.id);
|
|
||||||
|
|
||||||
this.addLink = document.createElement("a");
|
this.addLink = document.createElement("a");
|
||||||
this.addLink.className = "add-link";
|
this.addLink.className = "add-link";
|
||||||
this.addLink.href = "javascript:void(0)";
|
this.addLink.href = "javascript:void(0)";
|
||||||
this.addLink.textContent = this.dataset.addLinkLabel;
|
|
||||||
this.addLink.addEventListener("click", this);
|
this.addLink.addEventListener("click", this);
|
||||||
|
|
||||||
this.editLink = document.createElement("a");
|
this.editLink = document.createElement("a");
|
||||||
this.editLink.className = "edit-link";
|
this.editLink.className = "edit-link";
|
||||||
this.editLink.href = "javascript:void(0)";
|
this.editLink.href = "javascript:void(0)";
|
||||||
this.editLink.textContent = this.dataset.editLinkLabel;
|
|
||||||
this.editLink.addEventListener("click", this);
|
this.editLink.addEventListener("click", this);
|
||||||
|
|
||||||
this.invalidLabel = document.createElement("label");
|
this.invalidLabel = document.createElement("label");
|
||||||
this.invalidLabel.className = "invalid-label";
|
this.invalidLabel.className = "invalid-label";
|
||||||
this.invalidLabel.setAttribute("for", this.dropdown.popupBox.id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
|
if (!this.dropdown.popupBox.id) {
|
||||||
|
this.dropdown.popupBox.id = "select-" + Math.floor(Math.random() * 1000000);
|
||||||
|
}
|
||||||
|
this.labelElement.setAttribute("for", this.dropdown.popupBox.id);
|
||||||
|
this.invalidLabel.setAttribute("for", this.dropdown.popupBox.id);
|
||||||
|
|
||||||
// The document order, by default, controls tab order so keep that in mind if changing this.
|
// The document order, by default, controls tab order so keep that in mind if changing this.
|
||||||
this.appendChild(this.labelElement);
|
this.appendChild(this.labelElement);
|
||||||
this.appendChild(this.dropdown);
|
this.appendChild(this.dropdown);
|
||||||
|
@ -61,6 +62,8 @@ export default class RichPicker extends PaymentStateSubscriberMixin(HTMLElement)
|
||||||
this.classList.toggle("invalid-selected-option",
|
this.classList.toggle("invalid-selected-option",
|
||||||
!!errorText);
|
!!errorText);
|
||||||
this.invalidLabel.textContent = errorText;
|
this.invalidLabel.textContent = errorText;
|
||||||
|
this.addLink.textContent = this.dataset.addLinkLabel;
|
||||||
|
this.editLink.textContent = this.dataset.editLinkLabel;
|
||||||
}
|
}
|
||||||
|
|
||||||
get selectedOption() {
|
get selectedOption() {
|
||||||
|
@ -99,7 +102,8 @@ export default class RichPicker extends PaymentStateSubscriberMixin(HTMLElement)
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
let fieldNames = this.selectedRichOption.requiredFields;
|
let fieldNames = this.selectedRichOption.requiredFields || [];
|
||||||
|
|
||||||
// Return all field names that are empty or missing from the option.
|
// Return all field names that are empty or missing from the option.
|
||||||
return fieldNames.filter(name => !selectedOption.getAttribute(name));
|
return fieldNames.filter(name => !selectedOption.getAttribute(name));
|
||||||
}
|
}
|
||||||
|
|
|
@ -193,8 +193,12 @@
|
||||||
data-add-basic-card-title="&basicCard.addPage.title;"
|
data-add-basic-card-title="&basicCard.addPage.title;"
|
||||||
data-edit-basic-card-title="&basicCard.editPage.title;"
|
data-edit-basic-card-title="&basicCard.editPage.title;"
|
||||||
data-error-generic-save="&basicCardPage.error.genericSave;"
|
data-error-generic-save="&basicCardPage.error.genericSave;"
|
||||||
|
|
||||||
data-address-add-link-label="&basicCardPage.addressAddLink.label;"
|
data-address-add-link-label="&basicCardPage.addressAddLink.label;"
|
||||||
data-address-edit-link-label="&basicCardPage.addressEditLink.label;"
|
data-address-edit-link-label="&basicCardPage.addressEditLink.label;"
|
||||||
|
|
||||||
|
data-invalid-address-label="&invalidOption.label;"
|
||||||
|
data-address-field-separator="&address.fieldSeparator;"
|
||||||
data-billing-address-title-add="&billingAddress.addPage.title;"
|
data-billing-address-title-add="&billingAddress.addPage.title;"
|
||||||
data-billing-address-title-edit="&billingAddress.editPage.title;"
|
data-billing-address-title-edit="&billingAddress.editPage.title;"
|
||||||
data-back-button-label="&basicCardPage.backButton.label;"
|
data-back-button-label="&basicCardPage.backButton.label;"
|
||||||
|
|
|
@ -36,6 +36,7 @@ async function add_link(aOptions = {}) {
|
||||||
checkboxSelector: "basic-card-form .persist-checkbox",
|
checkboxSelector: "basic-card-form .persist-checkbox",
|
||||||
expectPersist: aOptions.expectDefaultCardPersist,
|
expectPersist: aOptions.expectDefaultCardPersist,
|
||||||
});
|
});
|
||||||
|
|
||||||
await spawnPaymentDialogTask(frame, async function checkState(testArgs = {}) {
|
await spawnPaymentDialogTask(frame, async function checkState(testArgs = {}) {
|
||||||
let {
|
let {
|
||||||
PaymentTestUtils: PTU,
|
PaymentTestUtils: PTU,
|
||||||
|
@ -163,12 +164,12 @@ async function add_link(aOptions = {}) {
|
||||||
ok(state["basic-card-page"].billingAddressGUID,
|
ok(state["basic-card-page"].billingAddressGUID,
|
||||||
"billingAddressGUID should be set when coming back from address-page");
|
"billingAddressGUID should be set when coming back from address-page");
|
||||||
|
|
||||||
let billingAddressSelect = content.document.querySelector("#billingAddressGUID");
|
let billingAddressPicker = Cu.waiveXrays(
|
||||||
|
content.document.querySelector("basic-card-form billing-address-picker"));
|
||||||
|
|
||||||
is(billingAddressSelect.childElementCount, 3,
|
is(billingAddressPicker.options.length, 3,
|
||||||
"Three options should exist in the billingAddressSelect");
|
"Three options should exist in the billingAddressPicker");
|
||||||
let selectedOption =
|
let selectedOption = billingAddressPicker.dropdown.selectedOption;
|
||||||
billingAddressSelect.children[billingAddressSelect.selectedIndex];
|
|
||||||
let selectedAddressGuid = selectedOption.value;
|
let selectedAddressGuid = selectedOption.value;
|
||||||
let lastAddress = Object.values(addressColn)[Object.keys(addressColn).length - 1];
|
let lastAddress = Object.values(addressColn)[Object.keys(addressColn).length - 1];
|
||||||
is(selectedAddressGuid, lastAddress.guid, "The select should have the new address selected");
|
is(selectedAddressGuid, lastAddress.guid, "The select should have the new address selected");
|
||||||
|
@ -411,152 +412,163 @@ add_task(async function test_edit_link() {
|
||||||
const args = {
|
const args = {
|
||||||
methodData: [PTU.MethodData.basicCard],
|
methodData: [PTU.MethodData.basicCard],
|
||||||
details: PTU.Details.total60USD,
|
details: PTU.Details.total60USD,
|
||||||
|
prefilledGuids,
|
||||||
};
|
};
|
||||||
await spawnInDialogForMerchantTask(PTU.ContentTasks.createAndShowRequest, async function check() {
|
await spawnInDialogForMerchantTask(
|
||||||
let {
|
PTU.ContentTasks.createAndShowRequest,
|
||||||
PaymentTestUtils: PTU,
|
async function check({prefilledGuids}) {
|
||||||
} = ChromeUtils.import("resource://testing-common/PaymentTestUtils.jsm", {});
|
let {
|
||||||
|
PaymentTestUtils: PTU,
|
||||||
|
} = ChromeUtils.import("resource://testing-common/PaymentTestUtils.jsm", {});
|
||||||
|
|
||||||
let editLink = content.document.querySelector("payment-method-picker .edit-link");
|
let editLink = content.document.querySelector("payment-method-picker .edit-link");
|
||||||
is(editLink.textContent, "Edit", "Edit link text");
|
is(editLink.textContent, "Edit", "Edit link text");
|
||||||
|
|
||||||
editLink.click();
|
editLink.click();
|
||||||
|
|
||||||
let state = await PTU.DialogContentUtils.waitForState(content, (state) => {
|
let state = await PTU.DialogContentUtils.waitForState(content, (state) => {
|
||||||
return state.page.id == "basic-card-page" && state["basic-card-page"].guid;
|
return state.page.id == "basic-card-page" && state["basic-card-page"].guid;
|
||||||
}, "Check edit page state");
|
}, "Check edit page state");
|
||||||
|
|
||||||
state = await PTU.DialogContentUtils.waitForState(content, (state) => {
|
state = await PTU.DialogContentUtils.waitForState(content, (state) => {
|
||||||
return Object.keys(state.savedBasicCards).length == 1 &&
|
return Object.keys(state.savedBasicCards).length == 1 &&
|
||||||
Object.keys(state.savedAddresses).length == 1;
|
Object.keys(state.savedAddresses).length == 1;
|
||||||
}, "Check card and address present at beginning of test");
|
}, "Check card and address present at beginning of test");
|
||||||
|
|
||||||
let title = content.document.querySelector("basic-card-form h2");
|
let title = content.document.querySelector("basic-card-form h2");
|
||||||
is(title.textContent, "Edit Credit Card", "Edit title should be set");
|
is(title.textContent, "Edit Credit Card", "Edit title should be set");
|
||||||
|
|
||||||
let saveButton = content.document.querySelector("basic-card-form .save-button");
|
let saveButton = content.document.querySelector("basic-card-form .save-button");
|
||||||
is(saveButton.textContent, "Update", "Save button has the correct label");
|
is(saveButton.textContent, "Update", "Save button has the correct label");
|
||||||
|
|
||||||
let card = Object.assign({}, PTU.BasicCards.JohnDoe);
|
let card = Object.assign({}, PTU.BasicCards.JohnDoe);
|
||||||
// cc-number cannot be modified
|
// cc-number cannot be modified
|
||||||
delete card["cc-number"];
|
delete card["cc-number"];
|
||||||
card["cc-exp-year"]++;
|
card["cc-exp-year"]++;
|
||||||
card["cc-exp-month"]++;
|
card["cc-exp-month"]++;
|
||||||
|
|
||||||
info("overwriting field values");
|
info("overwriting field values");
|
||||||
for (let [key, val] of Object.entries(card)) {
|
for (let [key, val] of Object.entries(card)) {
|
||||||
let field = content.document.getElementById(key);
|
let field = content.document.getElementById(key);
|
||||||
field.value = val;
|
field.value = val;
|
||||||
ok(!field.disabled, `Field #${key} shouldn't be disabled`);
|
ok(!field.disabled, `Field #${key} shouldn't be disabled`);
|
||||||
}
|
|
||||||
ok(content.document.getElementById("cc-number").disabled, "cc-number field should be disabled");
|
|
||||||
|
|
||||||
let billingAddressSelect = content.document.querySelector("#billingAddressGUID");
|
|
||||||
is(billingAddressSelect.childElementCount, 2,
|
|
||||||
"Two options should exist in the billingAddressSelect");
|
|
||||||
is(billingAddressSelect.selectedIndex, 1,
|
|
||||||
"The prefilled billing address should be selected by default");
|
|
||||||
|
|
||||||
info("Test clicking 'edit' on the empty option first");
|
|
||||||
billingAddressSelect.selectedIndex = 0;
|
|
||||||
|
|
||||||
let addressEditLink = content.document.querySelector(".billingAddressRow .edit-link");
|
|
||||||
addressEditLink.click();
|
|
||||||
state = await PTU.DialogContentUtils.waitForState(content, (state) => {
|
|
||||||
return state.page.id == "address-page" && !state["address-page"].guid;
|
|
||||||
}, "Clicking edit button when the empty option is selected will go to 'add' page (no guid)");
|
|
||||||
|
|
||||||
let addressTitle = content.document.querySelector("address-form h2");
|
|
||||||
is(addressTitle.textContent, "Add Billing Address",
|
|
||||||
"Address on add address page should be correct");
|
|
||||||
|
|
||||||
let addressBackButton = content.document.querySelector("address-form .back-button");
|
|
||||||
addressBackButton.click();
|
|
||||||
state = await PTU.DialogContentUtils.waitForState(content, (state) => {
|
|
||||||
return state.page.id == "basic-card-page" && state["basic-card-page"].guid &&
|
|
||||||
Object.keys(state.savedAddresses).length == 1;
|
|
||||||
}, "Check we're back at basic-card page with no state changed after adding");
|
|
||||||
|
|
||||||
info("Go back to previously selected option before clicking 'edit' now");
|
|
||||||
billingAddressSelect.selectedIndex = 1;
|
|
||||||
|
|
||||||
let selectedOption = billingAddressSelect.selectedOptions.length &&
|
|
||||||
billingAddressSelect.selectedOptions[0];
|
|
||||||
ok(selectedOption && selectedOption.value, "select should have a selected option value");
|
|
||||||
|
|
||||||
addressEditLink.click();
|
|
||||||
state = await PTU.DialogContentUtils.waitForState(content, (state) => {
|
|
||||||
return state.page.id == "address-page" && state["address-page"].guid;
|
|
||||||
}, "Check address page state (editing)");
|
|
||||||
|
|
||||||
is(addressTitle.textContent, "Edit Billing Address",
|
|
||||||
"Address on edit address page should be correct");
|
|
||||||
|
|
||||||
state = await PTU.DialogContentUtils.waitForState(content, (state) => {
|
|
||||||
return Object.keys(state.savedBasicCards).length == 1;
|
|
||||||
}, "Check card was not added again when clicking the 'edit' address button");
|
|
||||||
|
|
||||||
addressBackButton.click();
|
|
||||||
state = await PTU.DialogContentUtils.waitForState(content, (state) => {
|
|
||||||
return state.page.id == "basic-card-page" && state["basic-card-page"].guid &&
|
|
||||||
Object.keys(state.savedAddresses).length == 1;
|
|
||||||
}, "Check we're back at basic-card page with no state changed after editing");
|
|
||||||
|
|
||||||
for (let [key, val] of Object.entries(card)) {
|
|
||||||
let field = content.document.getElementById(key);
|
|
||||||
is(field.value, val, "Field should still have previous value entered");
|
|
||||||
}
|
|
||||||
|
|
||||||
selectedOption = billingAddressSelect.selectedOptions.length &&
|
|
||||||
billingAddressSelect.selectedOptions[0];
|
|
||||||
ok(selectedOption && selectedOption.value, "select should have a selected option value");
|
|
||||||
|
|
||||||
addressEditLink.click();
|
|
||||||
state = await PTU.DialogContentUtils.waitForState(content, (state) => {
|
|
||||||
return state.page.id == "address-page" && state["address-page"].guid;
|
|
||||||
}, "Check address page state (editing)");
|
|
||||||
|
|
||||||
info("modify some address fields");
|
|
||||||
for (let key of ["given-name", "tel", "organization", "street-address"]) {
|
|
||||||
let field = content.document.getElementById(key);
|
|
||||||
if (!field) {
|
|
||||||
ok(false, `${key} field not found`);
|
|
||||||
}
|
}
|
||||||
field.focus();
|
ok(content.document.getElementById("cc-number").disabled,
|
||||||
EventUtils.sendKey("BACK_SPACE", content.window);
|
"cc-number field should be disabled");
|
||||||
EventUtils.sendString("7", content.window);
|
|
||||||
ok(!field.disabled, `Field #${key} shouldn't be disabled`);
|
|
||||||
}
|
|
||||||
|
|
||||||
content.document.querySelector("address-form button.save-button").click();
|
let billingAddressPicker = Cu.waiveXrays(
|
||||||
state = await PTU.DialogContentUtils.waitForState(content, (state) => {
|
content.document.querySelector("basic-card-form billing-address-picker"));
|
||||||
return state.page.id == "basic-card-page" && state["basic-card-page"].guid &&
|
|
||||||
Object.keys(state.savedAddresses).length == 1;
|
|
||||||
}, "Check still only one address and we're back on basic-card page");
|
|
||||||
|
|
||||||
is(Object.values(state.savedAddresses)[0].tel, PTU.Addresses.TimBL.tel.slice(0, -1) + "7",
|
let initialSelectedAddressGuid = billingAddressPicker.dropdown.value;
|
||||||
"Check that address was edited and saved");
|
is(billingAddressPicker.options.length, 2,
|
||||||
|
"Two options should exist in the billingAddressPicker");
|
||||||
|
is(initialSelectedAddressGuid, prefilledGuids.address1GUID,
|
||||||
|
"The prefilled billing address should be selected by default");
|
||||||
|
|
||||||
content.document.querySelector("basic-card-form button.save-button").click();
|
info("Test clicking 'add' on the empty option first");
|
||||||
|
billingAddressPicker.dropdown.popupBox.focus();
|
||||||
|
content.fillField(billingAddressPicker.dropdown.popupBox, "");
|
||||||
|
|
||||||
state = await PTU.DialogContentUtils.waitForState(content, (state) => {
|
let addressEditLink = content.document.querySelector(".billingAddressRow .edit-link");
|
||||||
let cards = Object.entries(state.savedBasicCards);
|
ok(addressEditLink && !content.isVisible(addressEditLink),
|
||||||
return cards.length == 1 &&
|
"The edit link is hidden when empty option is selected");
|
||||||
cards[0][1]["cc-name"] == card["cc-name"];
|
|
||||||
}, "Check card was edited");
|
|
||||||
|
|
||||||
let cardGUIDs = Object.keys(state.savedBasicCards);
|
let addressAddLink = content.document.querySelector(".billingAddressRow .add-link");
|
||||||
is(cardGUIDs.length, 1, "Check there is still one card");
|
addressAddLink.click();
|
||||||
let savedCard = state.savedBasicCards[cardGUIDs[0]];
|
state = await PTU.DialogContentUtils.waitForState(content, (state) => {
|
||||||
is(savedCard["cc-number"], "************1111", "Card number should be masked and unmodified.");
|
return state.page.id == "address-page" && !state["address-page"].guid;
|
||||||
for (let [key, val] of Object.entries(card)) {
|
}, "Clicking add button when the empty option is selected will go to 'add' page (no guid)");
|
||||||
is(savedCard[key], val, "Check updated " + key);
|
|
||||||
}
|
|
||||||
|
|
||||||
state = await PTU.DialogContentUtils.waitForState(content, (state) => {
|
let addressTitle = content.document.querySelector("address-form h2");
|
||||||
return state.page.id == "payment-summary";
|
is(addressTitle.textContent, "Add Billing Address",
|
||||||
}, "Switched back to payment-summary");
|
"Address on add address page should be correct");
|
||||||
}, args);
|
|
||||||
|
let addressBackButton = content.document.querySelector("address-form .back-button");
|
||||||
|
addressBackButton.click();
|
||||||
|
state = await PTU.DialogContentUtils.waitForState(content, (state) => {
|
||||||
|
return state.page.id == "basic-card-page" && state["basic-card-page"].guid &&
|
||||||
|
Object.keys(state.savedAddresses).length == 1;
|
||||||
|
}, "Check we're back at basic-card page with no state changed after adding");
|
||||||
|
|
||||||
|
info("Go back to previously selected option before clicking 'edit' now");
|
||||||
|
billingAddressPicker.dropdown.value = initialSelectedAddressGuid;
|
||||||
|
|
||||||
|
let selectedOption = billingAddressPicker.dropdown.selectedOption;
|
||||||
|
ok(selectedOption && selectedOption.value, "select should have a selected option value");
|
||||||
|
|
||||||
|
addressEditLink.click();
|
||||||
|
state = await PTU.DialogContentUtils.waitForState(content, (state) => {
|
||||||
|
return state.page.id == "address-page" && state["address-page"].guid;
|
||||||
|
}, "Check address page state (editing)");
|
||||||
|
|
||||||
|
is(addressTitle.textContent, "Edit Billing Address",
|
||||||
|
"Address on edit address page should be correct");
|
||||||
|
|
||||||
|
state = await PTU.DialogContentUtils.waitForState(content, (state) => {
|
||||||
|
return Object.keys(state.savedBasicCards).length == 1;
|
||||||
|
}, "Check card was not added again when clicking the 'edit' address button");
|
||||||
|
|
||||||
|
addressBackButton.click();
|
||||||
|
state = await PTU.DialogContentUtils.waitForState(content, (state) => {
|
||||||
|
return state.page.id == "basic-card-page" && state["basic-card-page"].guid &&
|
||||||
|
Object.keys(state.savedAddresses).length == 1;
|
||||||
|
}, "Check we're back at basic-card page with no state changed after editing");
|
||||||
|
|
||||||
|
for (let [key, val] of Object.entries(card)) {
|
||||||
|
let field = content.document.getElementById(key);
|
||||||
|
is(field.value, val, "Field should still have previous value entered");
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedOption = billingAddressPicker.dropdown.selectedOption;
|
||||||
|
ok(selectedOption && selectedOption.value, "select should have a selected option value");
|
||||||
|
|
||||||
|
addressEditLink.click();
|
||||||
|
state = await PTU.DialogContentUtils.waitForState(content, (state) => {
|
||||||
|
return state.page.id == "address-page" && state["address-page"].guid;
|
||||||
|
}, "Check address page state (editing)");
|
||||||
|
|
||||||
|
info("modify some address fields");
|
||||||
|
for (let key of ["given-name", "tel", "organization", "street-address"]) {
|
||||||
|
let field = content.document.getElementById(key);
|
||||||
|
if (!field) {
|
||||||
|
ok(false, `${key} field not found`);
|
||||||
|
}
|
||||||
|
field.focus();
|
||||||
|
EventUtils.sendKey("BACK_SPACE", content.window);
|
||||||
|
EventUtils.sendString("7", content.window);
|
||||||
|
ok(!field.disabled, `Field #${key} shouldn't be disabled`);
|
||||||
|
}
|
||||||
|
|
||||||
|
content.document.querySelector("address-form button.save-button").click();
|
||||||
|
state = await PTU.DialogContentUtils.waitForState(content, (state) => {
|
||||||
|
return state.page.id == "basic-card-page" && state["basic-card-page"].guid &&
|
||||||
|
Object.keys(state.savedAddresses).length == 1;
|
||||||
|
}, "Check still only one address and we're back on basic-card page");
|
||||||
|
|
||||||
|
is(Object.values(state.savedAddresses)[0].tel, PTU.Addresses.TimBL.tel.slice(0, -1) + "7",
|
||||||
|
"Check that address was edited and saved");
|
||||||
|
|
||||||
|
content.document.querySelector("basic-card-form button.save-button").click();
|
||||||
|
|
||||||
|
state = await PTU.DialogContentUtils.waitForState(content, (state) => {
|
||||||
|
let cards = Object.entries(state.savedBasicCards);
|
||||||
|
return cards.length == 1 &&
|
||||||
|
cards[0][1]["cc-name"] == card["cc-name"];
|
||||||
|
}, "Check card was edited");
|
||||||
|
|
||||||
|
let cardGUIDs = Object.keys(state.savedBasicCards);
|
||||||
|
is(cardGUIDs.length, 1, "Check there is still one card");
|
||||||
|
let savedCard = state.savedBasicCards[cardGUIDs[0]];
|
||||||
|
is(savedCard["cc-number"], "************1111",
|
||||||
|
"Card number should be masked and unmodified.");
|
||||||
|
for (let [key, val] of Object.entries(card)) {
|
||||||
|
is(savedCard[key], val, "Check updated " + key);
|
||||||
|
}
|
||||||
|
|
||||||
|
state = await PTU.DialogContentUtils.waitForState(content, (state) => {
|
||||||
|
return state.page.id == "payment-summary";
|
||||||
|
}, "Switched back to payment-summary");
|
||||||
|
}, args);
|
||||||
});
|
});
|
||||||
|
|
||||||
add_task(async function test_invalid_network_card_edit() {
|
add_task(async function test_invalid_network_card_edit() {
|
||||||
|
|
|
@ -20,6 +20,7 @@ skip-if = os == "linux" || os == "win" # Bug 1493216
|
||||||
[test_basic_card_form.html]
|
[test_basic_card_form.html]
|
||||||
skip-if = debug || asan # Bug 1493349
|
skip-if = debug || asan # Bug 1493349
|
||||||
[test_basic_card_option.html]
|
[test_basic_card_option.html]
|
||||||
|
[test_billing_address_picker.html]
|
||||||
[test_completion_error_page.html]
|
[test_completion_error_page.html]
|
||||||
[test_currency_amount.html]
|
[test_currency_amount.html]
|
||||||
[test_labelled_checkbox.html]
|
[test_labelled_checkbox.html]
|
||||||
|
|
|
@ -90,6 +90,9 @@ add_task(async function test_initialState() {
|
||||||
add_task(async function test_backButton() {
|
add_task(async function test_backButton() {
|
||||||
let form = new AddressForm();
|
let form = new AddressForm();
|
||||||
form.dataset.backButtonLabel = "Back";
|
form.dataset.backButtonLabel = "Back";
|
||||||
|
await form.promiseReady;
|
||||||
|
display.appendChild(form);
|
||||||
|
|
||||||
await form.requestStore.setState({
|
await form.requestStore.setState({
|
||||||
page: {
|
page: {
|
||||||
id: "address-page",
|
id: "address-page",
|
||||||
|
@ -99,9 +102,6 @@ add_task(async function test_backButton() {
|
||||||
title: "Sample page title",
|
title: "Sample page title",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
await form.promiseReady;
|
|
||||||
display.appendChild(form);
|
|
||||||
await asyncElementRendered();
|
await asyncElementRendered();
|
||||||
|
|
||||||
let stateChangePromise = promiseStateChange(form.requestStore);
|
let stateChangePromise = promiseStateChange(form.requestStore);
|
||||||
|
|
|
@ -61,6 +61,14 @@ function checkCCForm(customEl, expectedCard) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createAddressRecord(source, props = {}) {
|
||||||
|
let address = Object.assign({}, source, props);
|
||||||
|
if (!address.name) {
|
||||||
|
address.name = `${address["given-name"]} ${address["family-name"]}`;
|
||||||
|
}
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
|
||||||
add_task(async function setup_once() {
|
add_task(async function setup_once() {
|
||||||
let templateFrame = document.getElementById("templateFrame");
|
let templateFrame = document.getElementById("templateFrame");
|
||||||
await SimpleTest.promiseFocus(templateFrame.contentWindow);
|
await SimpleTest.promiseFocus(templateFrame.contentWindow);
|
||||||
|
@ -70,6 +78,13 @@ add_task(async function setup_once() {
|
||||||
|
|
||||||
add_task(async function test_initialState() {
|
add_task(async function test_initialState() {
|
||||||
let form = new BasicCardForm();
|
let form = new BasicCardForm();
|
||||||
|
|
||||||
|
await form.requestStore.setState({
|
||||||
|
savedAddresses: {
|
||||||
|
"TimBLGUID": createAddressRecord(PTU.Addresses.TimBL),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
let {page} = form.requestStore.getState();
|
let {page} = form.requestStore.getState();
|
||||||
is(page.id, "payment-summary", "Check initial page");
|
is(page.id, "payment-summary", "Check initial page");
|
||||||
await form.promiseReady;
|
await form.promiseReady;
|
||||||
|
@ -79,6 +94,9 @@ add_task(async function test_initialState() {
|
||||||
|
|
||||||
// :-moz-ui-invalid, unlike :invalid, only applies to fields showing the error outline.
|
// :-moz-ui-invalid, unlike :invalid, only applies to fields showing the error outline.
|
||||||
let fieldsVisiblyInvalid = form.querySelectorAll(":-moz-ui-invalid");
|
let fieldsVisiblyInvalid = form.querySelectorAll(":-moz-ui-invalid");
|
||||||
|
for (let field of fieldsVisiblyInvalid) {
|
||||||
|
info("invalid field: " + field.localName + "#" + field.id + "." + field.className);
|
||||||
|
}
|
||||||
is(fieldsVisiblyInvalid.length, 0, "Check no fields are visibly invalid on an empty 'add' form");
|
is(fieldsVisiblyInvalid.length, 0, "Check no fields are visibly invalid on an empty 'add' form");
|
||||||
|
|
||||||
form.remove();
|
form.remove();
|
||||||
|
@ -116,18 +134,18 @@ add_task(async function test_saveButton() {
|
||||||
let form = new BasicCardForm();
|
let form = new BasicCardForm();
|
||||||
form.dataset.nextButtonLabel = "Next";
|
form.dataset.nextButtonLabel = "Next";
|
||||||
form.dataset.errorGenericSave = "Generic error";
|
form.dataset.errorGenericSave = "Generic error";
|
||||||
|
form.dataset.invalidAddressLabel = "Invalid";
|
||||||
|
|
||||||
await form.promiseReady;
|
await form.promiseReady;
|
||||||
display.appendChild(form);
|
display.appendChild(form);
|
||||||
|
|
||||||
let address1 = deepClone(PTU.Addresses.TimBL);
|
let address1 = createAddressRecord(PTU.Addresses.TimBL, {guid: "TimBLGUID"});
|
||||||
address1.guid = "TimBLGUID";
|
let address2 = createAddressRecord(PTU.Addresses.TimBL2, {guid: "TimBL2GUID"});
|
||||||
let address2 = deepClone(PTU.Addresses.TimBL2);
|
|
||||||
address2.guid = "TimBL2GUID";
|
|
||||||
|
|
||||||
|
|
||||||
await form.requestStore.setState({
|
await form.requestStore.setState({
|
||||||
request: {
|
request: {
|
||||||
paymentMethods,
|
paymentMethods,
|
||||||
|
paymentDetails: {},
|
||||||
},
|
},
|
||||||
savedAddresses: {
|
savedAddresses: {
|
||||||
[address1.guid]: deepClone(address1),
|
[address1.guid]: deepClone(address1),
|
||||||
|
@ -274,10 +292,8 @@ add_task(async function test_add_selectedShippingAddress() {
|
||||||
card1.guid = "9864798564";
|
card1.guid = "9864798564";
|
||||||
card1["cc-exp-year"] = 2011;
|
card1["cc-exp-year"] = 2011;
|
||||||
|
|
||||||
let address1 = deepClone(PTU.Addresses.TimBL);
|
let address1 = createAddressRecord(PTU.Addresses.TimBL, { guid: "TimBLGUID" });
|
||||||
address1.guid = "TimBLGUID";
|
let address2 = createAddressRecord(PTU.Addresses.TimBL2, { guid: "TimBL2GUID" });
|
||||||
let address2 = deepClone(PTU.Addresses.TimBL2);
|
|
||||||
address2.guid = "TimBL2GUID";
|
|
||||||
|
|
||||||
await form.requestStore.setState({
|
await form.requestStore.setState({
|
||||||
page: {
|
page: {
|
||||||
|
@ -312,8 +328,7 @@ add_task(async function test_add_noSelectedShippingAddress() {
|
||||||
card1.guid = "9864798564";
|
card1.guid = "9864798564";
|
||||||
card1["cc-exp-year"] = 2011;
|
card1["cc-exp-year"] = 2011;
|
||||||
|
|
||||||
let address1 = deepClone(PTU.Addresses.TimBL);
|
let address1 = createAddressRecord(PTU.Addresses.TimBL, { guid: "TimBLGUID" });
|
||||||
address1.guid = "TimBLGUID";
|
|
||||||
|
|
||||||
await form.requestStore.setState({
|
await form.requestStore.setState({
|
||||||
page: {
|
page: {
|
||||||
|
@ -352,8 +367,7 @@ add_task(async function test_edit() {
|
||||||
display.appendChild(form);
|
display.appendChild(form);
|
||||||
await asyncElementRendered();
|
await asyncElementRendered();
|
||||||
|
|
||||||
let address1 = deepClone(PTU.Addresses.TimBL);
|
let address1 = createAddressRecord(PTU.Addresses.TimBL, { guid: "TimBLGUID" });
|
||||||
address1.guid = "TimBLGUID";
|
|
||||||
|
|
||||||
info("test year before current");
|
info("test year before current");
|
||||||
let card1 = deepClone(PTU.BasicCards.JohnDoe);
|
let card1 = deepClone(PTU.BasicCards.JohnDoe);
|
||||||
|
@ -364,6 +378,7 @@ add_task(async function test_edit() {
|
||||||
await form.requestStore.setState({
|
await form.requestStore.setState({
|
||||||
request: {
|
request: {
|
||||||
paymentMethods,
|
paymentMethods,
|
||||||
|
paymentDetails: {},
|
||||||
},
|
},
|
||||||
page: {
|
page: {
|
||||||
id: "basic-card-page",
|
id: "basic-card-page",
|
||||||
|
@ -383,6 +398,7 @@ add_task(async function test_edit() {
|
||||||
is(form.saveButton.textContent, "Update", "Check label");
|
is(form.saveButton.textContent, "Update", "Check label");
|
||||||
is(form.querySelectorAll(":-moz-ui-invalid").length, 0,
|
is(form.querySelectorAll(":-moz-ui-invalid").length, 0,
|
||||||
"Check no fields are visibly invalid on an 'edit' form with a complete card");
|
"Check no fields are visibly invalid on an 'edit' form with a complete card");
|
||||||
|
|
||||||
checkCCForm(form, card1);
|
checkCCForm(form, card1);
|
||||||
ok(!form.saveButton.disabled, "Save button should be enabled upon edit for a valid card");
|
ok(!form.saveButton.disabled, "Save button should be enabled upon edit for a valid card");
|
||||||
ok(!form.acceptedCardsList.hidden, "Accepted card list should be visible when editing a card");
|
ok(!form.acceptedCardsList.hidden, "Accepted card list should be visible when editing a card");
|
||||||
|
@ -460,6 +476,17 @@ add_task(async function test_field_validity_updates() {
|
||||||
form.dataset.updateButtonLabel = "Update";
|
form.dataset.updateButtonLabel = "Update";
|
||||||
await form.promiseReady;
|
await form.promiseReady;
|
||||||
display.appendChild(form);
|
display.appendChild(form);
|
||||||
|
|
||||||
|
let address1 = createAddressRecord(PTU.Addresses.TimBL, {guid: "TimBLGUID"});
|
||||||
|
await form.requestStore.setState({
|
||||||
|
request: {
|
||||||
|
paymentMethods,
|
||||||
|
paymentDetails: {},
|
||||||
|
},
|
||||||
|
savedAddresses: {
|
||||||
|
[address1.guid]: deepClone(address1),
|
||||||
|
},
|
||||||
|
});
|
||||||
await asyncElementRendered();
|
await asyncElementRendered();
|
||||||
|
|
||||||
let ccNumber = form.form.querySelector("#cc-number");
|
let ccNumber = form.form.querySelector("#cc-number");
|
||||||
|
@ -468,6 +495,7 @@ add_task(async function test_field_validity_updates() {
|
||||||
let cscInput = form.form.querySelector("csc-input input");
|
let cscInput = form.form.querySelector("csc-input input");
|
||||||
let monthInput = form.form.querySelector("#cc-exp-month");
|
let monthInput = form.form.querySelector("#cc-exp-month");
|
||||||
let yearInput = form.form.querySelector("#cc-exp-year");
|
let yearInput = form.form.querySelector("#cc-exp-year");
|
||||||
|
let addressPicker = form.querySelector("#billingAddressGUID");
|
||||||
|
|
||||||
info("test with valid cc-number but missing cc-name");
|
info("test with valid cc-number but missing cc-name");
|
||||||
fillField(ccNumber, "4111111111111111");
|
fillField(ccNumber, "4111111111111111");
|
||||||
|
@ -487,6 +515,16 @@ add_task(async function test_field_validity_updates() {
|
||||||
ok(monthInput.checkValidity(), "cc-exp-month field is valid with a value");
|
ok(monthInput.checkValidity(), "cc-exp-month field is valid with a value");
|
||||||
ok(yearInput.checkValidity(), "cc-exp-year field is valid with a value");
|
ok(yearInput.checkValidity(), "cc-exp-year field is valid with a value");
|
||||||
ok(typeInput.checkValidity(), "cc-type field is valid with a value");
|
ok(typeInput.checkValidity(), "cc-type field is valid with a value");
|
||||||
|
|
||||||
|
// should auto-select the first billing address
|
||||||
|
ok(addressPicker.value, "An address is selected: " + addressPicker.value);
|
||||||
|
|
||||||
|
let fieldsVisiblyInvalid = form.querySelectorAll(":-moz-ui-invalid");
|
||||||
|
for (let field of fieldsVisiblyInvalid) {
|
||||||
|
info("invalid field: " + field.localName + "#" + field.id + "." + field.className);
|
||||||
|
}
|
||||||
|
is(fieldsVisiblyInvalid.length, 0, "No fields are visibly invalid");
|
||||||
|
|
||||||
ok(!form.saveButton.disabled, "Save button should not be disabled with good input");
|
ok(!form.saveButton.disabled, "Save button should not be disabled with good input");
|
||||||
|
|
||||||
info("edit to make the cc-number invalid");
|
info("edit to make the cc-number invalid");
|
||||||
|
@ -518,6 +556,17 @@ add_task(async function test_numberCustomValidityReset() {
|
||||||
form.dataset.updateButtonLabel = "Update";
|
form.dataset.updateButtonLabel = "Update";
|
||||||
await form.promiseReady;
|
await form.promiseReady;
|
||||||
display.appendChild(form);
|
display.appendChild(form);
|
||||||
|
|
||||||
|
let address1 = createAddressRecord(PTU.Addresses.TimBL, {guid: "TimBLGUID"});
|
||||||
|
await form.requestStore.setState({
|
||||||
|
request: {
|
||||||
|
paymentMethods,
|
||||||
|
paymentDetails: {},
|
||||||
|
},
|
||||||
|
savedAddresses: {
|
||||||
|
[address1.guid]: deepClone(address1),
|
||||||
|
},
|
||||||
|
});
|
||||||
await asyncElementRendered();
|
await asyncElementRendered();
|
||||||
|
|
||||||
fillField(form.querySelector("#cc-number"), "junk");
|
fillField(form.querySelector("#cc-number"), "junk");
|
||||||
|
|
|
@ -0,0 +1,133 @@
|
||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html>
|
||||||
|
<!--
|
||||||
|
Test the address-picker component
|
||||||
|
-->
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Test the billing-address-picker component</title>
|
||||||
|
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||||
|
<script type="application/javascript" src="/tests/SimpleTest/AddTask.js"></script>
|
||||||
|
<script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
|
||||||
|
<script src="payments_common.js"></script>
|
||||||
|
<script src="../../res/unprivileged-fallbacks.js"></script>
|
||||||
|
<script src="autofillEditForms.js"></script>
|
||||||
|
|
||||||
|
<link rel="stylesheet" type="text/css" href="../../res/containers/rich-picker.css"/>
|
||||||
|
<link rel="stylesheet" type="text/css" href="../../res/components/rich-select.css"/>
|
||||||
|
<link rel="stylesheet" type="text/css" href="../../res/components/address-option.css"/>
|
||||||
|
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p id="display">
|
||||||
|
<billing-address-picker id="picker1"
|
||||||
|
data-field-separator=", "
|
||||||
|
data-invalid-label="Picker1: Missing or Invalid"
|
||||||
|
selected-state-key="basic-card-page|billingAddressGUID"></billing-address-picker>
|
||||||
|
<select id="theOptions">
|
||||||
|
<option></option>
|
||||||
|
<option value="48bnds6854t">48bnds6854t</option>
|
||||||
|
<option value="68gjdh354j" selected="">68gjdh354j</option>
|
||||||
|
</select>
|
||||||
|
</p>
|
||||||
|
<div id="content" style="display: none">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<pre id="test">
|
||||||
|
</pre>
|
||||||
|
<script type="module">
|
||||||
|
/** Test the billing-address-picker component **/
|
||||||
|
|
||||||
|
import BillingAddressPicker from "../../res/containers/billing-address-picker.js";
|
||||||
|
|
||||||
|
let picker1 = document.getElementById("picker1");
|
||||||
|
let addresses = {
|
||||||
|
"48bnds6854t": {
|
||||||
|
"address-level1": "MI",
|
||||||
|
"address-level2": "Some City",
|
||||||
|
"country": "US",
|
||||||
|
"guid": "48bnds6854t",
|
||||||
|
"name": "Mr. Foo",
|
||||||
|
"postal-code": "90210",
|
||||||
|
"street-address": "123 Sesame Street,\nApt 40",
|
||||||
|
"tel": "+1 519 555-5555",
|
||||||
|
timeLastUsed: 200,
|
||||||
|
},
|
||||||
|
"68gjdh354j": {
|
||||||
|
"address-level1": "CA",
|
||||||
|
"address-level2": "Mountain View",
|
||||||
|
"country": "US",
|
||||||
|
"guid": "68gjdh354j",
|
||||||
|
"name": "Mrs. Bar",
|
||||||
|
"postal-code": "94041",
|
||||||
|
"street-address": "P.O. Box 123",
|
||||||
|
"tel": "+1 650 555-5555",
|
||||||
|
timeLastUsed: 300,
|
||||||
|
},
|
||||||
|
"abcde12345": {
|
||||||
|
"address-level2": "Mountain View",
|
||||||
|
"country": "US",
|
||||||
|
"guid": "abcde12345",
|
||||||
|
"name": "Mrs. Fields",
|
||||||
|
timeLastUsed: 100,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
add_task(async function test_empty() {
|
||||||
|
ok(picker1, "Check picker1 exists");
|
||||||
|
let {savedAddresses} = picker1.requestStore.getState();
|
||||||
|
is(Object.keys(savedAddresses).length, 0, "Check empty initial state");
|
||||||
|
is(picker1.editLink.hidden, true, "Check that picker edit link is hidden");
|
||||||
|
is(picker1.options.length, 1, "Check only the empty option is present");
|
||||||
|
ok(picker1.dropdown.selectedOption, "Has a selectedOption");
|
||||||
|
is(picker1.dropdown.value, "", "Has empty value");
|
||||||
|
|
||||||
|
// update state to trigger render without changing available addresses
|
||||||
|
picker1.requestStore.setState({
|
||||||
|
"basic-card-page": {
|
||||||
|
"someKey": "someValue",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await asyncElementRendered();
|
||||||
|
|
||||||
|
is(picker1.dropdown.popupBox.children.length, 1, "Check only the empty option is present");
|
||||||
|
ok(picker1.dropdown.selectedOption, "Has a selectedOption");
|
||||||
|
is(picker1.dropdown.value, "", "Has empty value");
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(async function test_getCurrentValue() {
|
||||||
|
picker1.requestStore.setState({
|
||||||
|
"basic-card-page": {
|
||||||
|
"billingAddressGUID": "68gjdh354j",
|
||||||
|
},
|
||||||
|
savedAddresses: addresses,
|
||||||
|
});
|
||||||
|
await asyncElementRendered();
|
||||||
|
|
||||||
|
picker1.dropdown.popupBox.value = "abcde12345";
|
||||||
|
|
||||||
|
is(picker1.options.length, 4, "Check we have options for each address + empty one");
|
||||||
|
is(picker1.getCurrentValue(picker1.requestStore.getState()), "abcde12345",
|
||||||
|
"Initial/current value reflects the <select>.value, " +
|
||||||
|
"not whatever is in the state at the selectedStateKey");
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(async function test_wrapPopupBox() {
|
||||||
|
let picker = new BillingAddressPicker();
|
||||||
|
picker.dropdown.popupBox = document.querySelector("#theOptions");
|
||||||
|
picker.dataset.invalidLabel = "Invalid";
|
||||||
|
picker.setAttribute("label", "The label");
|
||||||
|
picker.setAttribute("selected-state-key", "basic-card-page|billingAddressGUID");
|
||||||
|
|
||||||
|
document.querySelector("#display").appendChild(picker);
|
||||||
|
|
||||||
|
is(picker.labelElement.getAttribute("for"), "theOptions",
|
||||||
|
"The label points at the right element");
|
||||||
|
is(picker.invalidLabel.getAttribute("for"), "theOptions",
|
||||||
|
"The invalidLabel points at the right element");
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
Загрузка…
Ссылка в новой задаче