--- a/browser/components/payments/res/containers/address-form.js
+++ b/browser/components/payments/res/containers/address-form.js
@@ -33,24 +33,26 @@ export default class AddressForm extends
this.saveButton = document.createElement("button");
this.saveButton.className = "save-button primary";
this.saveButton.addEventListener("click", this);
this.persistCheckbox = new LabelledCheckbox();
this.persistCheckbox.className = "persist-checkbox";
this._errorFieldMap = {
- addressLine: "#street-address-container",
- city: "#address-level2-container",
- country: "#country-container",
- organization: "#organization-container",
- phone: "#tel-container",
- postalCode: "#postal-code-container",
- recipient: "#name-container",
- region: "#address-level1-container",
+ addressLine: "#street-address",
+ city: "#address-level2",
+ country: "#country",
+ organization: "#organization",
+ phone: "#tel",
+ postalCode: "#postal-code",
+ // Bug 1472283 is on file to support
+ // additional-name and family-name.
+ recipient: "#given-name",
+ region: "#address-level1",
};
// 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;
});
@@ -138,27 +140,37 @@ export default class AddressForm extends
} else {
// Adding a new record: default persistence to checked when in a not-private session
this.persistCheckbox.hidden = false;
this.persistCheckbox.checked = !state.isPrivate;
}
this.formHandler.loadRecord(record);
+ // Add validation to some address fields
+ let postalCodeInput = this.form.querySelector("#postal-code");
+ let addressLevel1Input = this.form.querySelector("#address-level1");
+ for (let element of [postalCodeInput, addressLevel1Input]) {
+ element.required = element.closest(`#${element.id}-container`).style.display != "none";
+ }
+
let shippingAddressErrors = request.paymentDetails.shippingAddressErrors;
for (let [errorName, errorSelector] of Object.entries(this._errorFieldMap)) {
- let container = document.querySelector(errorSelector);
+ let container = document.querySelector(errorSelector + "-container");
+ let field = document.querySelector(errorSelector);
+ let errorText = (shippingAddressErrors && shippingAddressErrors[errorName]) || "";
+ container.classList.toggle("error", !!errorText);
+ field.setCustomValidity(errorText);
let span = container.querySelector(".error-text");
if (!span) {
span = document.createElement("span");
span.className = "error-text";
container.appendChild(span);
}
- span.textContent = shippingAddressErrors[errorName];
- container.classList.toggle("error", !!shippingAddressErrors[errorName]);
+ span.textContent = errorText;
}
}
handleEvent(event) {
switch (event.type) {
case "click": {
this.onClick(event);
break;
--- a/browser/components/payments/test/mochitest/test_address_form.html
+++ b/browser/components/payments/test/mochitest/test_address_form.html
@@ -25,17 +25,17 @@ Test the address-form element
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
<script type="module">
/** Test the address-form element **/
-/* global sinon */
+/* global sinon, PaymentDialogUtils */
import AddressForm from "../../res/containers/address-form.js";
let display = document.getElementById("display");
function checkAddressForm(customEl, expectedAddress) {
const ADDRESS_PROPERTY_NAMES = [
"given-name",
@@ -52,16 +52,26 @@ function checkAddressForm(customEl, expe
for (let propName of ADDRESS_PROPERTY_NAMES) {
let expectedVal = expectedAddress[propName] || "";
is(document.getElementById(propName).value,
expectedVal.toString(),
`Check ${propName}`);
}
}
+function sendStringAndCheckValidity(element, string, isValid) {
+ element.focus();
+ while (element.value) {
+ sendKey("BACK_SPACE");
+ }
+ sendString(string);
+ ok(element.checkValidity() == isValid,
+ `${element.id} should be ${isValid ? "valid" : "invalid"}`);
+}
+
add_task(async function test_initialState() {
let form = new AddressForm();
let {page} = form.requestStore.getState();
is(page.id, "payment-summary", "Check initial page");
await form.promiseReady;
display.appendChild(form);
await asyncElementRendered();
is(page.id, "payment-summary", "Check initial page after appending");
@@ -270,12 +280,130 @@ add_task(async function test_restricted_
"country should be hidden");
ok(!isHidden(form.form.querySelector("#email")),
"email should be visible");
ok(!isHidden(form.form.querySelector("#tel")),
"tel should be visible");
form.remove();
});
+
+add_task(async function test_field_validation() {
+ let form = new AddressForm();
+ await form.promiseReady;
+ display.appendChild(form);
+ await asyncElementRendered();
+
+ let postalCodeInput = form.form.querySelector("#postal-code");
+ let addressLevel1Input = form.form.querySelector("#address-level1");
+ ok(!postalCodeInput.value, "postal-code should be empty by default");
+ ok(!addressLevel1Input.value, "address-level1 should be empty by default");
+ ok(!postalCodeInput.checkValidity(), "postal-code should be invalid by default");
+ ok(!addressLevel1Input.checkValidity(), "address-level1 should be invalid by default");
+
+ sendStringAndCheckValidity(postalCodeInput, "00001", true);
+ sendStringAndCheckValidity(addressLevel1Input, "CA", true);
+ sendStringAndCheckValidity(postalCodeInput, "94043", true);
+ sendStringAndCheckValidity(addressLevel1Input, "", false);
+ sendStringAndCheckValidity(postalCodeInput, "B4N4N4", true);
+ sendStringAndCheckValidity(addressLevel1Input, "MI", true);
+ sendStringAndCheckValidity(postalCodeInput, "R3J 3C7", true);
+ sendStringAndCheckValidity(addressLevel1Input, "", false);
+ sendStringAndCheckValidity(postalCodeInput, "11109", true);
+ sendStringAndCheckValidity(addressLevel1Input, "Nova Scotia", true);
+ sendStringAndCheckValidity(postalCodeInput, "06390-0001", true);
+
+ form.remove();
+});
+
+add_task(async function test_customValidity() {
+ let form = new AddressForm();
+ await form.promiseReady;
+ const state = {
+ page: {
+ id: "address-page",
+ },
+ "address-page": {
+ title: "Sample page title",
+ },
+ request: {
+ paymentDetails: {
+ shippingAddressErrors: {
+ addressLine: "Street address needs to start with a D",
+ city: "City needs to start with a B",
+ country: "Country needs to start with a C",
+ organization: "organization needs to start with an A",
+ phone: "Telephone needs to start with a 9",
+ postalCode: "Postal code needs to start with a 0",
+ recipient: "Name needs to start with a Z",
+ region: "Region needs to start with a Y",
+ },
+ },
+ },
+ };
+ await form.requestStore.setState(state);
+ display.appendChild(form);
+ await asyncElementRendered();
+
+ function checkValidationMessage(selector, property) {
+ is(form.form.querySelector(selector).validationMessage,
+ state.request.paymentDetails.shippingAddressErrors[property],
+ "Validation message should match for " + selector);
+ }
+
+ checkValidationMessage("#street-address", "addressLine");
+ checkValidationMessage("#address-level2", "city");
+ checkValidationMessage("#country", "country");
+ checkValidationMessage("#organization", "organization");
+ checkValidationMessage("#tel", "phone");
+ checkValidationMessage("#postal-code", "postalCode");
+ checkValidationMessage("#given-name", "recipient");
+ checkValidationMessage("#address-level1", "region");
+
+ form.remove();
+});
+
+add_task(async function test_field_validation() {
+ sinon.stub(PaymentDialogUtils, "getFormFormat").returns({
+ addressLevel1Label: "state",
+ postalCodeLabel: "US",
+ fieldsOrder: [
+ {fieldId: "name", newLine: true},
+ {fieldId: "organization", newLine: true},
+ {fieldId: "street-address", newLine: true},
+ {fieldId: "address-level2"},
+ ],
+ });
+
+ let form = new AddressForm();
+ await form.promiseReady;
+ const state = {
+ page: {
+ id: "address-page",
+ },
+ "address-page": {
+ title: "Sample page title",
+ },
+ request: {
+ paymentDetails: {
+ shippingAddressErrors: {},
+ },
+ },
+ };
+ await form.requestStore.setState(state);
+ display.appendChild(form);
+ await asyncElementRendered();
+
+ let postalCodeInput = form.form.querySelector("#postal-code");
+ let addressLevel1Input = form.form.querySelector("#address-level1");
+ ok(!postalCodeInput.value, "postal-code should be empty by default");
+ ok(!addressLevel1Input.value, "address-level1 should be empty by default");
+ ok(postalCodeInput.checkValidity(),
+ "postal-code should be valid by default when it is not visible");
+ ok(addressLevel1Input.checkValidity(),
+ "address-level1 should be valid by default when it is not visible");
+
+ form.remove();
+});
</script>
</body>
</html>