Bug 1375382 - [Form Autofill] Handle filling in country field select element. r=seanlee
authorLuke Chang <lchang@mozilla.com>
Fri, 28 Jul 2017 12:22:50 +0800
changeset 373806 40da3955d4a16972cffaf926944ccd0a9a713cd4
parent 373805 bf9cf6f393eb9e15af7db0ce56f94c4cc840bbf6
child 373807 408174c3122a23b91b53dfc340034f789808654f
push id32309
push userkwierso@gmail.com
push dateFri, 11 Aug 2017 00:36:58 +0000
treeherdermozilla-central@d0068f9051be [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersseanlee
bugs1375382
milestone57.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 1375382 - [Form Autofill] Handle filling in country field select element. r=seanlee MozReview-Commit-ID: ADwTlx7MKaJ
browser/extensions/formautofill/FormAutofillUtils.jsm
browser/extensions/formautofill/ProfileStorage.jsm
browser/extensions/formautofill/test/unit/test_autofillFormFields.js
browser/extensions/formautofill/test/unit/test_getAdaptedProfiles.js
browser/extensions/formautofill/test/unit/test_transformFields.js
--- a/browser/extensions/formautofill/FormAutofillUtils.jsm
+++ b/browser/extensions/formautofill/FormAutofillUtils.jsm
@@ -5,16 +5,22 @@
 "use strict";
 
 this.EXPORTED_SYMBOLS = ["FormAutofillUtils"];
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 const ADDRESS_REFERENCES = "chrome://formautofill/content/addressReferences.js";
 
+// TODO: We only support US in MVP. We are going to support more countries in
+//       bug 1370193.
+const ALTERNATIVE_COUNTRY_NAMES = {
+  "US": ["US", "United States of America", "United States", "America", "U.S.", "USA", "U.S.A.", "U.S.A"],
+};
+
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 
 this.FormAutofillUtils = {
   get AUTOFILL_FIELDS_THRESHOLD() { return 3; },
 
   _fieldNameInfo: {
     "name": "name",
@@ -41,16 +47,18 @@ this.FormAutofillUtils = {
     "tel-extension": "tel",
     "email": "email",
     "cc-name": "creditCard",
     "cc-number": "creditCard",
     "cc-exp-month": "creditCard",
     "cc-exp-year": "creditCard",
   },
   _addressDataLoaded: false,
+  _collators: {},
+  _reAlternativeCountryNames: {},
 
   isAddressField(fieldName) {
     return !!this._fieldNameInfo[fieldName] && !this.isCreditCardField(fieldName);
   },
 
   isCreditCardField(fieldName) {
     return this._fieldNameInfo[fieldName] == "creditCard";
   },
@@ -213,108 +221,183 @@ this.FormAutofillUtils = {
     if (!this._addressDataLoaded) {
       Object.assign(this, this.loadDataFromScript(ADDRESS_REFERENCES));
       this._addressDataLoaded = true;
     }
     return this.addressData[`data/${country}`] || this.addressData["data/US"];
   },
 
   /**
+   * Get the collators based on the specified country.
+   * @param   {string} country The specified country.
+   * @returns {array} An array containing several collator objects.
+   */
+  getCollators(country) {
+    // TODO: Only one language should be used at a time per country. The locale
+    //       of the page should be taken into account to do this properly.
+    //       We are going to support more countries in bug 1370193 and this
+    //       should be addressed when we start to implement that bug.
+
+    if (!this._collators[country]) {
+      let dataset = this.getCountryAddressData(country);
+      let languages = dataset.languages ? dataset.languages.split("~") : [dataset.lang];
+      this._collators[country] = languages.map(lang => new Intl.Collator(lang, {sensitivity: "base", ignorePunctuation: true}));
+    }
+    return this._collators[country];
+  },
+
+  /**
+   * Use alternative country name list to identify a country code from a
+   * specified country name.
+   * @param   {string} countryName A country name to be identified
+   * @param   {string} [countrySpecified] A country code indicating that we only
+   *                                      search its alternative names if specified.
+   * @returns {string} The matching country code.
+   */
+  identifyCountryCode(countryName, countrySpecified) {
+    let countries = countrySpecified ? [countrySpecified] : Object.keys(ALTERNATIVE_COUNTRY_NAMES);
+
+    for (let country of countries) {
+      let collators = this.getCollators(country);
+
+      let alternativeCountryNames = ALTERNATIVE_COUNTRY_NAMES[country];
+      let reAlternativeCountryNames = this._reAlternativeCountryNames[country];
+      if (!reAlternativeCountryNames) {
+        reAlternativeCountryNames = this._reAlternativeCountryNames[country] = [];
+      }
+
+      for (let i = 0; i < alternativeCountryNames.length; i++) {
+        let name = alternativeCountryNames[i];
+        let reName = reAlternativeCountryNames[i];
+        if (!reName) {
+          reName = reAlternativeCountryNames[i] = new RegExp("\\b" + this.escapeRegExp(name) + "\\b", "i");
+        }
+
+        if (this.strCompare(name, countryName, collators) || reName.test(countryName)) {
+          return country;
+        }
+      }
+    }
+
+    return null;
+  },
+
+  /**
    * Find the option element from select element.
    * 1. Try to find the locale using the country from address.
    * 2. First pass try to find exact match.
    * 3. Second pass try to identify values from address value and options,
    *    and look for a match.
    * @param   {DOMElement} selectEl
    * @param   {object} address
    * @param   {string} fieldName
    * @returns {DOMElement}
    */
   findSelectOption(selectEl, address, fieldName) {
     let value = address[fieldName];
     if (!value) {
       return null;
     }
 
-    let dataset = this.getCountryAddressData(address.country);
-    let collator = new Intl.Collator(dataset.lang, {sensitivity: "base", ignorePunctuation: true});
+    let country = address.country || this.DEFAULT_COUNTRY_CODE;
+    let dataset = this.getCountryAddressData(country);
+    let collators = this.getCollators(country);
 
     for (let option of selectEl.options) {
-      if (this.strCompare(value, option.value, collator) ||
-          this.strCompare(value, option.text, collator)) {
+      if (this.strCompare(value, option.value, collators) ||
+          this.strCompare(value, option.text, collators)) {
         return option;
       }
     }
 
-    if (fieldName === "address-level1") {
-      if (!Array.isArray(dataset.sub_keys)) {
-        dataset.sub_keys = dataset.sub_keys.split("~");
-      }
-      if (!Array.isArray(dataset.sub_names)) {
-        dataset.sub_names = dataset.sub_names.split("~");
-      }
-      let keys = dataset.sub_keys;
-      let names = dataset.sub_names;
-      let identifiedValue = this.identifyValue(keys, names, value, collator);
+    switch (fieldName) {
+      case "address-level1": {
+        if (!Array.isArray(dataset.sub_keys)) {
+          dataset.sub_keys = dataset.sub_keys.split("~");
+        }
+        if (!Array.isArray(dataset.sub_names)) {
+          dataset.sub_names = dataset.sub_names.split("~");
+        }
+        let keys = dataset.sub_keys;
+        let names = dataset.sub_names;
+        let identifiedValue = this.identifyValue(keys, names, value, collators);
 
-      // No point going any further if we cannot identify value from address
-      if (identifiedValue === undefined) {
-        return null;
+        // No point going any further if we cannot identify value from address
+        if (!identifiedValue) {
+          return null;
+        }
+
+        // Go through options one by one to find a match.
+        // Also check if any option contain the address-level1 key.
+        let pattern = new RegExp("\\b" + this.escapeRegExp(identifiedValue) + "\\b", "i");
+        for (let option of selectEl.options) {
+          let optionValue = this.identifyValue(keys, names, option.value, collators);
+          let optionText = this.identifyValue(keys, names, option.text, collators);
+          if (identifiedValue === optionValue || identifiedValue === optionText || pattern.test(option.value)) {
+            return option;
+          }
+        }
+        break;
       }
-
-      // Go through options one by one to find a match.
-      // Also check if any option contain the address-level1 key.
-      let pattern = new RegExp(`\\b${identifiedValue}\\b`, "i");
-      for (let option of selectEl.options) {
-        let optionValue = this.identifyValue(keys, names, option.value, collator);
-        let optionText = this.identifyValue(keys, names, option.text, collator);
-        if (identifiedValue === optionValue || identifiedValue === optionText || pattern.test(option.value)) {
-          return option;
+      case "country": {
+        if (ALTERNATIVE_COUNTRY_NAMES[value]) {
+          for (let option of selectEl.options) {
+            if (this.identifyCountryCode(option.text, value) || this.identifyCountryCode(option.value, value)) {
+              return option;
+            }
+          }
         }
+        break;
       }
     }
 
-    if (fieldName === "country") {
-      // TODO: Support matching countries (Bug 1375382)
-    }
-
     return null;
   },
 
   /**
    * Try to match value with keys and names, but always return the key.
    * @param   {array<string>} keys
    * @param   {array<string>} names
    * @param   {string} value
-   * @param   {object} collator
+   * @param   {array} collators
    * @returns {string}
    */
-  identifyValue(keys, names, value, collator) {
-    let resultKey = keys.find(key => this.strCompare(value, key, collator));
+  identifyValue(keys, names, value, collators) {
+    let resultKey = keys.find(key => this.strCompare(value, key, collators));
     if (resultKey) {
       return resultKey;
     }
 
-    let index = names.findIndex(name => this.strCompare(value, name, collator));
+    let index = names.findIndex(name => this.strCompare(value, name, collators));
     if (index !== -1) {
       return keys[index];
     }
 
     return null;
   },
 
   /**
    * Compare if two strings are the same.
    * @param   {string} a
    * @param   {string} b
-   * @param   {object} collator
+   * @param   {array} collators
    * @returns {boolean}
    */
-  strCompare(a = "", b = "", collator) {
-    return !collator.compare(a, b);
+  strCompare(a = "", b = "", collators) {
+    return collators.some(collator => !collator.compare(a, b));
+  },
+
+  /**
+   * Escaping user input to be treated as a literal string within a regular
+   * expression.
+   * @param   {string} string
+   * @returns {string}
+   */
+  escapeRegExp(string) {
+    return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
   },
 
   /**
    * Get formatting information of a given country
    * @param   {string} country
    * @returns {object}
    *         {
    *           {string} addressLevel1Label
@@ -339,10 +422,14 @@ this.FormAutofillUtils = {
     let elements = root.querySelectorAll("[data-localization]");
     for (let element of elements) {
       element.textContent = bundle.GetStringFromName(element.getAttribute("data-localization"));
       element.removeAttribute("data-localization");
     }
   },
 };
 
+XPCOMUtils.defineLazyGetter(this.FormAutofillUtils, "DEFAULT_COUNTRY_CODE", () => {
+  return Services.prefs.getCharPref("browser.search.countryCode", "US");
+});
+
 this.log = null;
 this.FormAutofillUtils.defineLazyLogGetter(this, this.EXPORTED_SYMBOLS[0]);
--- a/browser/extensions/formautofill/ProfileStorage.jsm
+++ b/browser/extensions/formautofill/ProfileStorage.jsm
@@ -1211,19 +1211,17 @@ class Addresses extends AutofillRecords 
         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);
+        let tel = PhoneNumber.Parse(address.tel, address.country || FormAutofillUtils.DEFAULT_COUNTRY_CODE);
         if (tel) {
           if (tel.countryCode) {
             address["tel-country-code"] = tel.countryCode;
           }
           if (tel.nationalNumber) {
             address["tel-national"] = tel.nationalNumber;
           }
 
@@ -1296,43 +1294,40 @@ class Addresses extends AutofillRecords 
     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) {
+    let country;
+
     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;
-      }
+      country = address.country.toUpperCase();
     } else if (address["country-name"]) {
-      for (let region in REGION_NAMES) {
-        if (REGION_NAMES[region].toLowerCase() == address["country-name"].toLowerCase()) {
-          address.country = region;
-          break;
-        }
-      }
+      country = FormAutofillUtils.identifyCountryCode(address["country-name"]);
     }
+
+    // Only values included in the region list will be saved.
+    if (country && REGION_NAMES[country]) {
+      address.country = country;
+    } else {
+      delete address.country;
+    }
+
     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 region = address["tel-country-code"] || address.country || FormAutofillUtils.DEFAULT_COUNTRY_CODE;
     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"];
--- a/browser/extensions/formautofill/test/unit/test_autofillFormFields.js
+++ b/browser/extensions/formautofill/test/unit/test_autofillFormFields.js
@@ -247,36 +247,55 @@ const TESTCASES_INPUT_UNCHANGED = [
     },
     expectedResult: {
       "country": "US",
       "state": "",
     },
   },
 ];
 
-const TESTCASES_US_STATES = [
+const TESTCASES_FILL_SELECT = [
+  // US States
   {
-    description: "Form with US states select elements; with lower case state key",
+    description: "Form with US states select elements",
     document: `<form><select id="state" autocomplete="shipping address-level1">
                  <option value=""></option>
                  <option value="CA">California</option>
                </select></form>`,
     addressFieldDetails: [
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "address-level1", "element": {}},
     ],
     profileData: {
       "guid": "123",
       "country": "US",
-      "address-level1": "ca",
+      "address-level1": "CA",
     },
     expectedResult: {
       "state": "CA",
     },
   },
   {
+    description: "Form with US states select elements; with lower case state key",
+    document: `<form><select id="state" autocomplete="shipping address-level1">
+                 <option value=""></option>
+                 <option value="ca">ca</option>
+               </select></form>`,
+    addressFieldDetails: [
+      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "address-level1", "element": {}},
+    ],
+    profileData: {
+      "guid": "123",
+      "country": "US",
+      "address-level1": "CA",
+    },
+    expectedResult: {
+      "state": "ca",
+    },
+  },
+  {
     description: "Form with US states select elements; with state name and extra spaces",
     document: `<form><select id="state" autocomplete="shipping address-level1">
                  <option value=""></option>
                  <option value="CA">CA</option>
                </select></form>`,
     addressFieldDetails: [
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "address-level1", "element": {}},
     ],
@@ -302,16 +321,103 @@ const TESTCASES_US_STATES = [
       "guid": "123",
       "country": "US",
       "address-level1": "WA",
     },
     expectedResult: {
       "state": "US-WA",
     },
   },
+
+  // Country
+  {
+    description: "Form with country select elements",
+    document: `<form><select id="country" autocomplete="country">
+                 <option value=""></option>
+                 <option value="US">United States</option>
+               </select></form>`,
+    addressFieldDetails: [
+      {"section": "", "addressType": "", "contactType": "", "fieldName": "country", "element": {}},
+    ],
+    profileData: {
+      "guid": "123",
+      "country": "US",
+    },
+    expectedResult: {
+      "country": "US",
+    },
+  },
+  {
+    description: "Form with country select elements; with lower case key",
+    document: `<form><select id="country" autocomplete="country">
+                 <option value=""></option>
+                 <option value="us">us</option>
+               </select></form>`,
+    addressFieldDetails: [
+      {"section": "", "addressType": "", "contactType": "", "fieldName": "country", "element": {}},
+    ],
+    profileData: {
+      "guid": "123",
+      "country": "US",
+    },
+    expectedResult: {
+      "country": "us",
+    },
+  },
+  {
+    description: "Form with country select elements; with alternative name 1",
+    document: `<form><select id="country" autocomplete="country">
+                 <option value=""></option>
+                 <option value="XX">United States</option>
+               </select></form>`,
+    addressFieldDetails: [
+      {"section": "", "addressType": "", "contactType": "", "fieldName": "country", "element": {}},
+    ],
+    profileData: {
+      "guid": "123",
+      "country": "US",
+    },
+    expectedResult: {
+      "country": "XX",
+    },
+  },
+  {
+    description: "Form with country select elements; with alternative name 2",
+    document: `<form><select id="country" autocomplete="country">
+                 <option value=""></option>
+                 <option value="XX">America</option>
+               </select></form>`,
+    addressFieldDetails: [
+      {"section": "", "addressType": "", "contactType": "", "fieldName": "country", "element": {}},
+    ],
+    profileData: {
+      "guid": "123",
+      "country": "US",
+    },
+    expectedResult: {
+      "country": "XX",
+    },
+  },
+  {
+    description: "Form with country select elements; with partial matching value",
+    document: `<form><select id="country" autocomplete="country">
+                 <option value=""></option>
+                 <option value="XX">Ship to America</option>
+               </select></form>`,
+    addressFieldDetails: [
+      {"section": "", "addressType": "", "contactType": "", "fieldName": "country", "element": {}},
+    ],
+    profileData: {
+      "guid": "123",
+      "country": "US",
+    },
+    expectedResult: {
+      "country": "XX",
+    },
+  },
 ];
 
 function do_test(testcases, testFn) {
   for (let tc of testcases) {
     (function() {
       let testcase = tc;
       add_task(async function() {
         do_print("Starting testcase: " + testcase.description);
@@ -383,17 +489,17 @@ do_test(TESTCASES_INPUT_UNCHANGED, (test
         reject(`${event.type} event should not fire`);
       };
       element.addEventListener("change", cleaner);
       element.addEventListener("input", cleaner);
     }),
   ];
 });
 
-do_test(TESTCASES_US_STATES, (testcase, element) => {
+do_test(TESTCASES_FILL_SELECT, (testcase, element) => {
   let id = element.id;
   return [
     new Promise(resolve => {
       element.addEventListener("input", () => {
         Assert.equal(element.value, testcase.expectedResult[id],
                     "Check the " + id + " field was filled with correct data");
         resolve();
       }, {once: true});
--- a/browser/extensions/formautofill/test/unit/test_getAdaptedProfiles.js
+++ b/browser/extensions/formautofill/test/unit/test_getAdaptedProfiles.js
@@ -112,118 +112,141 @@ const TESTCASES = [
   },
   {
     description: "Form with exact matching options in select",
     document: `<form>
                <select autocomplete="address-level1">
                  <option id="option-address-level1-XX" value="XX">Dummy</option>
                  <option id="option-address-level1-CA" value="CA">California</option>
                </select>
+               <select autocomplete="country">
+                 <option id="option-country-XX" value="XX">Dummy</option>
+                 <option id="option-country-US" value="US">United States</option>
+               </select>
                </form>`,
     profileData: [Object.assign({}, DEFAULT_PROFILE)],
     expectedResult: [{
       "guid": "123",
       "street-address": "2 Harrison St\nline2\nline3",
       "-moz-street-address-one-line": "2 Harrison St line2 line3",
       "address-line1": "2 Harrison St",
       "address-line2": "line2",
       "address-line3": "line3",
       "address-level1": "CA",
       "country": "US",
     }],
     expectedOptionElements: [{
       "address-level1": "option-address-level1-CA",
+      "country": "option-country-US",
     }],
   },
   {
     description: "Form with inexact matching options in select",
     document: `<form>
                <select autocomplete="address-level1">
                  <option id="option-address-level1-XX" value="XX">Dummy</option>
                  <option id="option-address-level1-OO" value="OO">California</option>
                </select>
+               <select autocomplete="country">
+                 <option id="option-country-XX" value="XX">Dummy</option>
+                 <option id="option-country-OO" value="OO">United States</option>
+               </select>
                </form>`,
     profileData: [Object.assign({}, DEFAULT_PROFILE)],
     expectedResult: [{
       "guid": "123",
       "street-address": "2 Harrison St\nline2\nline3",
       "-moz-street-address-one-line": "2 Harrison St line2 line3",
       "address-line1": "2 Harrison St",
       "address-line2": "line2",
       "address-line3": "line3",
       "address-level1": "CA",
       "country": "US",
     }],
     expectedOptionElements: [{
       "address-level1": "option-address-level1-OO",
+      "country": "option-country-OO",
     }],
   },
   {
     description: "Form with value-omitted options in select",
     document: `<form>
                <select autocomplete="address-level1">
                  <option id="option-address-level1-1" value="">Dummy</option>
                  <option id="option-address-level1-2" value="">California</option>
                </select>
+               <select autocomplete="country">
+                 <option id="option-country-1" value="">Dummy</option>
+                 <option id="option-country-2" value="">United States</option>
+               </select>
                </form>`,
     profileData: [Object.assign({}, DEFAULT_PROFILE)],
     expectedResult: [{
       "guid": "123",
       "street-address": "2 Harrison St\nline2\nline3",
       "-moz-street-address-one-line": "2 Harrison St line2 line3",
       "address-line1": "2 Harrison St",
       "address-line2": "line2",
       "address-line3": "line3",
       "address-level1": "CA",
       "country": "US",
     }],
     expectedOptionElements: [{
       "address-level1": "option-address-level1-2",
+      "country": "option-country-2",
     }],
   },
   {
     description: "Form with options with the same value in select",
     document: `<form>
                <select autocomplete="address-level1">
                  <option id="option-address-level1-same1" value="same">Dummy</option>
                  <option id="option-address-level1-same2" value="same">California</option>
                </select>
+               <select autocomplete="country">
+                 <option id="option-country-same1" value="sametoo">Dummy</option>
+                 <option id="option-country-same2" value="sametoo">United States</option>
+               </select>
                </form>`,
     profileData: [Object.assign({}, DEFAULT_PROFILE)],
     expectedResult: [{
       "guid": "123",
       "street-address": "2 Harrison St\nline2\nline3",
       "-moz-street-address-one-line": "2 Harrison St line2 line3",
       "address-line1": "2 Harrison St",
       "address-line2": "line2",
       "address-line3": "line3",
       "address-level1": "CA",
       "country": "US",
     }],
     expectedOptionElements: [{
       "address-level1": "option-address-level1-same2",
+      "country": "option-country-same2",
     }],
   },
   {
     description: "Form without matching options in select",
     document: `<form>
                <select autocomplete="address-level1">
                  <option id="option-address-level1-dummy1" value="">Dummy</option>
                  <option id="option-address-level1-dummy2" value="">Dummy 2</option>
                </select>
+               <select autocomplete="country">
+                 <option id="option-country-dummy1" value="">Dummy</option>
+                 <option id="option-country-dummy2" value="">Dummy 2</option>
+               </select>
                </form>`,
     profileData: [Object.assign({}, DEFAULT_PROFILE)],
     expectedResult: [{
       "guid": "123",
       "street-address": "2 Harrison St\nline2\nline3",
       "-moz-street-address-one-line": "2 Harrison St line2 line3",
       "address-line1": "2 Harrison St",
       "address-line2": "line2",
       "address-line3": "line3",
-      "country": "US",
     }],
   },
 ];
 
 for (let testcase of TESTCASES) {
   add_task(async function() {
     do_print("Starting testcase: " + testcase.description);
 
--- a/browser/extensions/formautofill/test/unit/test_transformFields.js
+++ b/browser/extensions/formautofill/test/unit/test_transformFields.js
@@ -305,16 +305,46 @@ const ADDRESS_NORMALIZE_TESTCASES = [
       "country-name": "united states",
     },
     expectedResult: {
       "country": "US",
       "country-name": "United States",
     },
   },
   {
+    description: "Has alternative \"country-name\"",
+    address: {
+      "country-name": "america",
+    },
+    expectedResult: {
+      "country": "US",
+      "country-name": "United States",
+    },
+  },
+  {
+    description: "Has \"country-name\" as a substring",
+    address: {
+      "country-name": "test america test",
+    },
+    expectedResult: {
+      "country": "US",
+      "country-name": "United States",
+    },
+  },
+  {
+    description: "Has \"country-name\" as part of a word",
+    address: {
+      "country-name": "TRUST",
+    },
+    expectedResult: {
+      "country": undefined,
+      "country-name": "",
+    },
+  },
+  {
     description: "Has unknown \"country-name\"",
     address: {
       "country-name": "unknown country name",
     },
     expectedResult: {
       "country": undefined,
       "country-name": "",
     },