Bug 1352324 - (Part 6) Store tel in E.164 format and calculate its components while reading. r=MattN
authorLuke Chang <lchang@mozilla.com>
Tue, 23 May 2017 19:16:04 +0800
changeset 369126 7aec8351b1db920b32fd7244f9f6cd890f00230c
parent 369125 b69772478582d37a3649ff2bee1eb8299a39e5e6
child 369127 93af9545a6f4d50257c55fccfa0477c1c8ce321d
push id46554
push userlchang@mozilla.com
push dateMon, 17 Jul 2017 05:46:56 +0000
treeherderautoland@7aec8351b1db [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersMattN
bugs1352324
milestone56.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1352324 - (Part 6) Store tel in E.164 format and calculate its components while reading. r=MattN MozReview-Commit-ID: 2XdbcYFLR8R
browser/extensions/formautofill/FormAutofillUtils.jsm
browser/extensions/formautofill/ProfileStorage.jsm
browser/extensions/formautofill/test/browser/head.js
browser/extensions/formautofill/test/mochitest/test_basic_autocomplete_form.html
browser/extensions/formautofill/test/mochitest/test_formautofill_preview_highlight.html
browser/extensions/formautofill/test/mochitest/test_on_address_submission.html
browser/extensions/formautofill/test/unit/test_addressRecords.js
browser/extensions/formautofill/test/unit/test_transformFields.js
--- a/browser/extensions/formautofill/FormAutofillUtils.jsm
+++ b/browser/extensions/formautofill/FormAutofillUtils.jsm
@@ -24,16 +24,22 @@ this.FormAutofillUtils = {
     "address-line2": "address",
     "address-line3": "address",
     "address-level1": "address",
     "address-level2": "address",
     "postal-code": "address",
     "country": "address",
     "country-name": "address",
     "tel": "tel",
+    "tel-country-code": "tel",
+    "tel-national": "tel",
+    "tel-area-code": "tel",
+    "tel-local": "tel",
+    "tel-local-prefix": "tel",
+    "tel-local-suffix": "tel",
     "email": "email",
     "cc-name": "creditCard",
     "cc-number": "creditCard",
     "cc-exp-month": "creditCard",
     "cc-exp-year": "creditCard",
   },
   _addressDataLoaded: false,
 
--- a/browser/extensions/formautofill/ProfileStorage.jsm
+++ b/browser/extensions/formautofill/ProfileStorage.jsm
@@ -21,26 +21,32 @@
  *       additional-name,
  *       family-name,
  *       organization,         // Company
  *       street-address,       // (Multiline)
  *       address-level2,       // City/Town
  *       address-level1,       // Province (Standardized code if possible)
  *       postal-code,
  *       country,              // ISO 3166
- *       tel,
+ *       tel,                  // Stored in E.164 format
  *       email,
  *
- *       // computed fields (These fields are not stored in the file as they are
- *       // generated at runtime.)
+ *       // computed fields (These fields are computed based on the above fields
+ *       // and are not allowed to be modified directly.)
  *       name,
  *       address-line1,
  *       address-line2,
  *       address-line3,
  *       country-name,
+ *       tel-country-code,
+ *       tel-national,
+ *       tel-area-code,
+ *       tel-local,
+ *       tel-local-prefix,
+ *       tel-local-suffix,
  *
  *       // metadata
  *       timeCreated,          // in ms
  *       timeLastUsed,         // in ms
  *       timeLastModified,     // in ms
  *       timesUsed
  *     }
  *   ],
@@ -52,18 +58,18 @@
  *       // credit card fields
  *       cc-name,
  *       cc-number-encrypted,
  *       cc-number-masked,     // e.g. ************1234
  *       cc-exp-month,
  *       cc-exp-year,          // 2-digit year will be converted to 4 digits
  *                             // upon saving
  *
- *       // computed fields (These fields are not stored in the file as they are
- *       // generated at runtime.)
+ *       // computed fields (These fields are computed based on the above fields
+ *       // and are not allowed to be modified directly.)
  *       cc-given-name,
  *       cc-additional-name,
  *       cc-family-name,
  *
  *       // metadata
  *       timeCreated,          // in ms
  *       timeLastUsed,         // in ms
  *       timeLastModified,     // in ms
@@ -86,16 +92,18 @@ Cu.import("resource://gre/modules/Servic
 Cu.import("resource://gre/modules/osfile.jsm");
 
 Cu.import("resource://formautofill/FormAutofillUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "JSONFile",
                                   "resource://gre/modules/JSONFile.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "FormAutofillNameUtils",
                                   "resource://formautofill/FormAutofillNameUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PhoneNumber",
+                                  "resource://formautofill/phonenumberutils/PhoneNumber.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "gUUIDGenerator",
                                    "@mozilla.org/uuid-generator;1",
                                    "nsIUUIDGenerator");
 
 XPCOMUtils.defineLazyGetter(this, "REGION_NAMES", function() {
   let regionNames = {};
   let countries = Services.strings.createBundle("chrome://global/locale/regionNames.properties").getSimpleEnumeration();
@@ -127,20 +135,29 @@ const VALID_ADDRESS_FIELDS = [
 ];
 
 const STREET_ADDRESS_COMPONENTS = [
   "address-line1",
   "address-line2",
   "address-line3",
 ];
 
+const TEL_COMPONENTS = [
+  "tel-country-code",
+  "tel-national",
+  "tel-area-code",
+  "tel-local",
+  "tel-local-prefix",
+  "tel-local-suffix",
+];
+
 const VALID_ADDRESS_COMPUTED_FIELDS = [
   "name",
   "country-name",
-].concat(STREET_ADDRESS_COMPONENTS);
+].concat(STREET_ADDRESS_COMPONENTS, TEL_COMPONENTS);
 
 const VALID_CREDIT_CARD_FIELDS = [
   "cc-name",
   "cc-number-encrypted",
   "cc-number-masked",
   "cc-exp-month",
   "cc-exp-year",
 ];
@@ -548,54 +565,104 @@ class Addresses extends AutofillRecords 
       if (address.country && REGION_NAMES[address.country]) {
         address["country-name"] = REGION_NAMES[address.country];
       } else {
         address["country-name"] = "";
       }
       hasNewComputedFields = true;
     }
 
+    // Compute tel
+    if (!("tel-national" in address)) {
+      if (address.tel) {
+        // Set "US" as the default region as we only support "en-US" for now.
+        let browserCountryCode = Services.prefs.getCharPref("browser.search.countryCode", "US");
+        let tel = PhoneNumber.Parse(address.tel, address.country || browserCountryCode);
+        if (tel) {
+          if (tel.countryCode) {
+            address["tel-country-code"] = tel.countryCode;
+          }
+          if (tel.nationalNumber) {
+            address["tel-national"] = tel.nationalNumber;
+          }
+
+          // PhoneNumberUtils doesn't support parsing the components of a telephone
+          // number so we hard coded the parser for US numbers only. We will need
+          // to figure out how to parse numbers from other regions when we support
+          // new countries in the future.
+          if (tel.nationalNumber && tel.countryCode == "+1") {
+            let telComponents = tel.nationalNumber.match(/(\d{3})((\d{3})(\d{4}))$/);
+            if (telComponents) {
+              address["tel-area-code"] = telComponents[1];
+              address["tel-local"] = telComponents[2];
+              address["tel-local-prefix"] = telComponents[3];
+              address["tel-local-suffix"] = telComponents[4];
+            }
+          }
+        } else {
+          // Treat "tel" as "tel-national" directly if it can't be parsed.
+          address["tel-national"] = address.tel;
+        }
+      }
+
+      TEL_COMPONENTS.forEach(c => {
+        address[c] = address[c] || "";
+      });
+    }
+
     return hasNewComputedFields;
   }
 
   _normalizeFields(address) {
-    // Normalize name
-    if (address.name) {
-      let nameParts = FormAutofillNameUtils.splitName(address.name);
-      if (!address["given-name"] && nameParts.given) {
-        address["given-name"] = nameParts.given;
-      }
-      if (!address["additional-name"] && nameParts.middle) {
-        address["additional-name"] = nameParts.middle;
-      }
-      if (!address["family-name"] && nameParts.family) {
-        address["family-name"] = nameParts.family;
-      }
-      delete address.name;
+    this._normalizeName(address);
+    this._normalizeAddress(address);
+    this._normalizeCountry(address);
+    this._normalizeTel(address);
+  }
+
+  _normalizeName(address) {
+    if (!address.name) {
+      return;
     }
 
-    // Normalize address lines
-    if (STREET_ADDRESS_COMPONENTS.some(c => address[c])) {
-      // Treat "street-address" as "address-line1" if it contains only one line
-      // and "address-line1" is omitted.
-      if (!address["address-line1"] && address["street-address"] &&
-          !address["street-address"].includes("\n")) {
-        address["address-line1"] = address["street-address"];
-        delete address["street-address"];
-      }
+    let nameParts = FormAutofillNameUtils.splitName(address.name);
+    if (!address["given-name"] && nameParts.given) {
+      address["given-name"] = nameParts.given;
+    }
+    if (!address["additional-name"] && nameParts.middle) {
+      address["additional-name"] = nameParts.middle;
+    }
+    if (!address["family-name"] && nameParts.family) {
+      address["family-name"] = nameParts.family;
+    }
+    delete address.name;
+  }
 
-      // Concatenate "address-line*" if "street-address" is omitted.
-      if (!address["street-address"]) {
-        address["street-address"] = STREET_ADDRESS_COMPONENTS.map(c => address[c]).join("\n");
-      }
-
-      STREET_ADDRESS_COMPONENTS.forEach(c => delete address[c]);
+  _normalizeAddress(address) {
+    if (STREET_ADDRESS_COMPONENTS.every(c => !address[c])) {
+      return;
     }
 
-    // Normalize country
+    // Treat "street-address" as "address-line1" if it contains only one line
+    // and "address-line1" is omitted.
+    if (!address["address-line1"] && address["street-address"] &&
+        !address["street-address"].includes("\n")) {
+      address["address-line1"] = address["street-address"];
+      delete address["street-address"];
+    }
+
+    // Concatenate "address-line*" if "street-address" is omitted.
+    if (!address["street-address"]) {
+      address["street-address"] = STREET_ADDRESS_COMPONENTS.map(c => address[c]).join("\n");
+    }
+
+    STREET_ADDRESS_COMPONENTS.forEach(c => delete address[c]);
+  }
+
+  _normalizeCountry(address) {
     if (address.country) {
       let country = address.country.toUpperCase();
       // Only values included in the region list will be saved.
       if (REGION_NAMES[country]) {
         address.country = country;
       } else {
         delete address.country;
       }
@@ -605,16 +672,48 @@ class Addresses extends AutofillRecords 
           address.country = region;
           break;
         }
       }
     }
     delete address["country-name"];
   }
 
+  _normalizeTel(address) {
+    if (!address.tel && TEL_COMPONENTS.every(c => !address[c])) {
+      return;
+    }
+
+    // Set "US" as the default region as we only support "en-US" for now.
+    let browserCountryCode = Services.prefs.getCharPref("browser.search.countryCode", "US");
+    let region = address["tel-country-code"] || address.country || browserCountryCode;
+    let number;
+
+    if (address.tel) {
+      number = address.tel;
+    } else if (address["tel-national"]) {
+      number = address["tel-national"];
+    } else if (address["tel-local"]) {
+      number = (address["tel-area-code"] || "") + address["tel-local"];
+    } else if (address["tel-local-prefix"] && address["tel-local-suffix"]) {
+      number = (address["tel-area-code"] || "") + address["tel-local-prefix"] + address["tel-local-suffix"];
+    }
+
+    let tel = PhoneNumber.Parse(number, region);
+    if (tel) {
+      // Force to save numbers in E.164 format if parse success.
+      address.tel = tel.internationalNumber;
+    } else if (!address.tel) {
+      // Save the original number anyway if "tel" is omitted.
+      address.tel = number;
+    }
+
+    TEL_COMPONENTS.forEach(c => delete address[c]);
+  }
+
   /**
    * Merge new address into the specified address if mergeable.
    *
    * @param  {string} guid
    *         Indicates which address to merge.
    * @param  {Object} address
    *         The new address used to merge into the old one.
    * @returns {boolean}
--- a/browser/extensions/formautofill/test/browser/head.js
+++ b/browser/extensions/formautofill/test/browser/head.js
@@ -14,17 +14,17 @@ const TEST_ADDRESS_1 = {
   "additional-name": "R.",
   "family-name": "Smith",
   organization: "World Wide Web Consortium",
   "street-address": "32 Vassar Street\nMIT Room 32-G524",
   "address-level2": "Cambridge",
   "address-level1": "MA",
   "postal-code": "02139",
   country: "US",
-  tel: "+1 617 253 5702",
+  tel: "+16172535702",
   email: "timbl@w3.org",
 };
 
 const TEST_ADDRESS_2 = {
   "street-address": "Some Address",
   country: "US",
 };
 
--- a/browser/extensions/formautofill/test/mochitest/test_basic_autocomplete_form.html
+++ b/browser/extensions/formautofill/test/mochitest/test_basic_autocomplete_form.html
@@ -19,23 +19,23 @@ Form autofill test: simple form address 
 
 "use strict";
 
 const {FormAutofillUtils} = SpecialPowers.Cu.import("resource://formautofill/FormAutofillUtils.jsm");
 
 let MOCK_STORAGE = [{
   organization: "Sesame Street",
   "street-address": "123 Sesame Street.\n2-line\n3-line",
-  tel: "1-345-345-3456",
+  tel: "+13453453456",
   country: "US",
   "address-level1": "NY",
 }, {
   organization: "Mozilla",
   "street-address": "331 E. Evelyn Avenue\n2-line\n3-line",
-  tel: "1-650-903-0800",
+  tel: "+16509030800",
   country: "US",
   "address-level1": "CA",
 }];
 
 function checkElementFilled(element, expectedvalue) {
   return [
     new Promise(resolve => {
       element.addEventListener("input", function onInput() {
@@ -82,31 +82,31 @@ function checkFormFilled(address) {
 
 async function setupAddressStorage() {
   await addAddress(MOCK_STORAGE[0]);
   await addAddress(MOCK_STORAGE[1]);
 }
 
 async function setupFormHistory() {
   await updateFormHistory([
-    {op: "add", fieldname: "tel", value: "1-234-567-890"},
+    {op: "add", fieldname: "tel", value: "+1234567890"},
     {op: "add", fieldname: "email", value: "foo@mozilla.com"},
   ]);
 }
 
 initPopupListener();
 
 // Form with history only.
 add_task(async function history_only_menu_checking() {
   await setupFormHistory();
 
   await setInput("#tel", "");
   doKey("down");
   await expectPopup();
-  checkMenuEntries(["1-234-567-890"], false);
+  checkMenuEntries(["+1234567890"], false);
 });
 
 // Form with both history and address storage.
 add_task(async function check_menu_when_both_existed() {
   await setupAddressStorage();
 
   await setInput("#organization", "");
   doKey("down");
@@ -172,17 +172,17 @@ add_task(async function check_fields_aft
   await checkFormFilled(MOCK_STORAGE[1]);
 });
 
 // Fallback to history search after autofill address.
 add_task(async function check_fallback_after_form_autofill() {
   await setInput("#tel", "");
   doKey("down");
   await expectPopup();
-  checkMenuEntries(["1-234-567-890"], false);
+  checkMenuEntries(["+1234567890"], false);
 });
 
 // Resume form autofill once all the autofilled fileds are changed.
 add_task(async function check_form_autofill_resume() {
   document.querySelector("#tel").blur();
   document.querySelector("#form1").reset();
   await setInput("#tel", "");
   doKey("down");
--- a/browser/extensions/formautofill/test/mochitest/test_formautofill_preview_highlight.html
+++ b/browser/extensions/formautofill/test/mochitest/test_formautofill_preview_highlight.html
@@ -18,23 +18,23 @@ Form autofill test: preview and highligh
 /* import-globals-from formautofill_common.js */
 
 "use strict";
 
 let defaultTextColor;
 const MOCK_STORAGE = [{
   organization: "Sesame Street",
   "street-address": "123 Sesame Street.",
-  tel: "1-345-345-3456",
+  tel: "+13453453456",
 }, {
   organization: "Mozilla",
   "street-address": "331 E. Evelyn Avenue",
 }, {
   organization: "Tel org",
-  tel: "2-222-333-444",
+  tel: "+12223334444",
 }];
 
 // We could not get ManuallyManagedState of element now, so directly check if
 // filter and text color style are applied.
 function checkFieldPreview(elem, expectedText) {
   const computedStyle = window.getComputedStyle(elem);
   const isStyleApplied = computedStyle.getPropertyValue("filter") !== "none" &&
                          computedStyle.getPropertyValue("color") !== defaultTextColor;
--- a/browser/extensions/formautofill/test/mochitest/test_on_address_submission.html
+++ b/browser/extensions/formautofill/test/mochitest/test_on_address_submission.html
@@ -17,21 +17,21 @@ Form autofill test: check if address is 
 /* import-globals-from ../../../../../toolkit/components/satchel/test/satchel_common.js */
 /* import-globals-from formautofill_common.js */
 
 "use strict";
 
 let TEST_ADDRESSES = [{
   organization: "Sesame Street",
   "street-address": "123 Sesame Street.",
-  tel: "1-345-345-3456",
+  tel: "+13453453456",
 }, {
   organization: "Mozilla",
   "street-address": "331 E. Evelyn Avenue",
-  tel: "1-650-903-0800",
+  tel: "+16509030800",
 }];
 
 initPopupListener();
 
 // Submit first address for saving.
 add_task(async function check_storage_after_form_submitted() {
   // We already verified the first time use case in browser test
   await SpecialPowers.pushPrefEnv({
--- a/browser/extensions/formautofill/test/unit/test_addressRecords.js
+++ b/browser/extensions/formautofill/test/unit/test_addressRecords.js
@@ -11,17 +11,17 @@ const TEST_ADDRESS_1 = {
   "additional-name": "John",
   "family-name": "Berners-Lee",
   organization: "World Wide Web Consortium",
   "street-address": "32 Vassar Street\nMIT Room 32-G524",
   "address-level2": "Cambridge",
   "address-level1": "MA",
   "postal-code": "02139",
   country: "US",
-  tel: "+1 617 253 5702",
+  tel: "+16172535702",
   email: "timbl@w3.org",
 };
 
 const TEST_ADDRESS_2 = {
   "street-address": "Some Address",
   country: "US",
 };
 
@@ -43,67 +43,67 @@ const TEST_ADDRESS_WITH_INVALID_FIELD = 
 };
 
 const MERGE_TESTCASES = [
   {
     description: "Merge a superset",
     addressInStorage: {
       "given-name": "Timothy",
       "street-address": "331 E. Evelyn Avenue",
-      "tel": "1-650-903-0800",
+      "tel": "+16509030800",
     },
     addressToMerge: {
       "given-name": "Timothy",
       "street-address": "331 E. Evelyn Avenue",
-      "tel": "1-650-903-0800",
+      "tel": "+16509030800",
       country: "US",
     },
     expectedAddress: {
       "given-name": "Timothy",
       "street-address": "331 E. Evelyn Avenue",
-      "tel": "1-650-903-0800",
+      "tel": "+16509030800",
       country: "US",
     },
   },
   {
     description: "Merge a subset",
     addressInStorage: {
       "given-name": "Timothy",
       "street-address": "331 E. Evelyn Avenue",
-      "tel": "1-650-903-0800",
+      "tel": "+16509030800",
       country: "US",
     },
     addressToMerge: {
       "given-name": "Timothy",
       "street-address": "331 E. Evelyn Avenue",
-      "tel": "1-650-903-0800",
+      "tel": "+16509030800",
     },
     expectedAddress: {
       "given-name": "Timothy",
       "street-address": "331 E. Evelyn Avenue",
-      "tel": "1-650-903-0800",
+      "tel": "+16509030800",
       country: "US",
     },
   },
   {
     description: "Merge an address with partial overlaps",
     addressInStorage: {
       "given-name": "Timothy",
       "street-address": "331 E. Evelyn Avenue",
-      "tel": "1-650-903-0800",
+      "tel": "+16509030800",
     },
     addressToMerge: {
       "street-address": "331 E. Evelyn Avenue",
-      "tel": "1-650-903-0800",
+      "tel": "+16509030800",
       country: "US",
     },
     expectedAddress: {
       "given-name": "Timothy",
       "street-address": "331 E. Evelyn Avenue",
-      "tel": "1-650-903-0800",
+      "tel": "+16509030800",
       country: "US",
     },
   },
 ];
 
 let do_check_record_matches = (recordWithMeta, record) => {
   for (let key in record) {
     do_check_eq(recordWithMeta[key], record[key]);
--- a/browser/extensions/formautofill/test/unit/test_transformFields.js
+++ b/browser/extensions/formautofill/test/unit/test_transformFields.js
@@ -100,16 +100,94 @@ const ADDRESS_COMPUTE_TESTCASES = [
     address: {
       "country": "US",
     },
     expectedResult: {
       "country": "US",
       "country-name": "United States",
     },
   },
+
+  // Tel
+  {
+    description: "\"tel\" with US country code",
+    address: {
+      "tel": "+16172535702",
+    },
+    expectedResult: {
+      "tel": "+16172535702",
+      "tel-country-code": "+1",
+      "tel-national": "6172535702",
+      "tel-area-code": "617",
+      "tel-local": "2535702",
+      "tel-local-prefix": "253",
+      "tel-local-suffix": "5702",
+    },
+  },
+  {
+    description: "\"tel\" with TW country code (the components won't be parsed)",
+    address: {
+      "tel": "+886212345678",
+    },
+    expectedResult: {
+      "tel": "+886212345678",
+      "tel-country-code": "+886",
+      "tel-national": "0212345678",
+      "tel-area-code": "",
+      "tel-local": "",
+      "tel-local-prefix": "",
+      "tel-local-suffix": "",
+    },
+  },
+  {
+    description: "\"tel\" without country code so use \"US\" as default resion",
+    address: {
+      "tel": "6172535702",
+    },
+    expectedResult: {
+      "tel": "+16172535702",
+      "tel-country-code": "+1",
+      "tel-national": "6172535702",
+      "tel-area-code": "617",
+      "tel-local": "2535702",
+      "tel-local-prefix": "253",
+      "tel-local-suffix": "5702",
+    },
+  },
+  {
+    description: "\"tel\" without country code but \"country\" is \"TW\"",
+    address: {
+      "tel": "0212345678",
+      "country": "TW",
+    },
+    expectedResult: {
+      "tel": "+886212345678",
+      "tel-country-code": "+886",
+      "tel-national": "0212345678",
+      "tel-area-code": "",
+      "tel-local": "",
+      "tel-local-prefix": "",
+      "tel-local-suffix": "",
+    },
+  },
+  {
+    description: "\"tel\" can't be parsed so leave it as-is",
+    address: {
+      "tel": "12345",
+    },
+    expectedResult: {
+      "tel": "12345",
+      "tel-country-code": "",
+      "tel-national": "12345",
+      "tel-area-code": "",
+      "tel-local": "",
+      "tel-local-prefix": "",
+      "tel-local-suffix": "",
+    },
+  },
 ];
 
 const ADDRESS_NORMALIZE_TESTCASES = [
   // Empty
   {
     description: "Empty address",
     address: {
     },
@@ -150,17 +228,16 @@ const ADDRESS_NORMALIZE_TESTCASES = [
       "given-name": "Timothy",
     },
     expectedResult: {
       "given-name": "Timothy",
       "family-name": "Doe",
     },
   },
 
-
   // Address
   {
     description: "Has \"address-line1~3\" and \"street-address\" is omitted",
     address: {
       "address-line1": "line1",
       "address-line2": "line2",
       "address-line3": "line3",
     },
@@ -269,16 +346,107 @@ const ADDRESS_NORMALIZE_TESTCASES = [
     address: {
       "country": "CA",
     },
     expectedResult: {
       "country": undefined,
       "country-name": "",
     },
   },
+
+  // Tel
+  {
+    description: "Has \"tel\" with country code",
+    address: {
+      "tel": "+16172535702",
+    },
+    expectedResult: {
+      "tel": "+16172535702",
+    },
+  },
+  {
+    description: "Has \"tel\" without country code but \"country\" is set",
+    address: {
+      "tel": "0212345678",
+      "country": "TW",
+    },
+    expectedResult: {
+      "tel": "+886212345678",
+    },
+  },
+  {
+    description: "Has \"tel\" without country code and \"country\" so use \"US\" as default region",
+    address: {
+      "tel": "6172535702",
+    },
+    expectedResult: {
+      "tel": "+16172535702",
+    },
+  },
+  {
+    description: "\"tel\" can't be parsed so leave it as-is",
+    address: {
+      "tel": "12345",
+    },
+    expectedResult: {
+      "tel": "12345",
+    },
+  },
+  {
+    description: "Has \"tel-national\" and \"tel-country-code\"",
+    address: {
+      "tel-national": "0212345678",
+      "tel-country-code": "+886",
+    },
+    expectedResult: {
+      "tel": "+886212345678",
+    },
+  },
+  {
+    description: "Has \"tel-national\" and \"country\"",
+    address: {
+      "tel-national": "0212345678",
+      "country": "TW",
+    },
+    expectedResult: {
+      "tel": "+886212345678",
+    },
+  },
+  {
+    description: "Has \"tel-national\", \"tel-country-code\" and \"country\"",
+    address: {
+      "tel-national": "0212345678",
+      "tel-country-code": "+886",
+      "country": "US",
+    },
+    expectedResult: {
+      "tel": "+886212345678",
+    },
+  },
+  {
+    description: "Has \"tel-area-code\" and \"tel-local\"",
+    address: {
+      "tel-area-code": "617",
+      "tel-local": "2535702",
+    },
+    expectedResult: {
+      "tel": "+16172535702",
+    },
+  },
+  {
+    description: "Has \"tel-area-code\", \"tel-local-prefix\" and \"tel-local-suffix\"",
+    address: {
+      "tel-area-code": "617",
+      "tel-local-prefix": "253",
+      "tel-local-suffix": "5702",
+    },
+    expectedResult: {
+      "tel": "+16172535702",
+    },
+  },
 ];
 
 const CREDIT_CARD_COMPUTE_TESTCASES = [
   // Empty
   {
     description: "Empty credit card",
     creditCard: {
     },