Bug 1352324 - (Part 6) Store tel in E.164 format and calculate its components while reading. r=MattN draft
authorLuke Chang <lchang@mozilla.com>
Tue, 23 May 2017 19:16:04 +0800
changeset 609618 3289b1abdb0804160cf84873ebc7c0d554290024
parent 609617 3b70c07592943df74ed58aa85e51d573ebb7d99b
child 637593 02c0995883dfd8de325068912bace7ddd5c60b40
push id68602
push userbmo:lchang@mozilla.com
push dateMon, 17 Jul 2017 03:27:15 +0000
reviewersMattN
bugs1352324
milestone56.0a1
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: {
     },