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 423761 64e427f215f75bd6415e6e3de4f3f839854315f8
parent 423760 8cc5993a9104c4bea3fb476baf3cbe0bca9007f0
child 423762 f6117c7b1161fd89b055ce3cd34f41df610d1631
push id1517
push userjlorenzo@mozilla.com
push dateThu, 14 Sep 2017 16:50:54 +0000
treeherdermozilla-release@3b41fd564418 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersseanlee
bugs1375382
milestone56.0
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": "",
     },