Bug 1359978 - (Part 1) Implement AutofillRecords as a base class for handling address records. r=MattN, r=steveck draft
authorLuke Chang <lchang@mozilla.com>
Fri, 28 Apr 2017 16:00:49 -0700
changeset 577586 f4aeb206de537f6b6b532b09fe2b5ee6599890d9
parent 577554 e66dedabe582ba7b394aee4f89ed70fe389b3c46
child 577587 20a9cd010e448275dd1fd848c669224b08dfcdc5
push id58724
push userbmo:lchang@mozilla.com
push dateMon, 15 May 2017 02:25:44 +0000
reviewersMattN, steveck
bugs1359978
milestone55.0a1
Bug 1359978 - (Part 1) Implement AutofillRecords as a base class for handling address records. r=MattN, r=steveck MozReview-Commit-ID: J6NL44iZ0zu
browser/extensions/formautofill/FormAutofillParent.jsm
browser/extensions/formautofill/ProfileStorage.jsm
browser/extensions/formautofill/test/unit/test_addressRecords.js
browser/extensions/formautofill/test/unit/test_enabledStatus.js
browser/extensions/formautofill/test/unit/test_profileStorage.js
browser/extensions/formautofill/test/unit/test_savedFieldNames.js
browser/extensions/formautofill/test/unit/test_transformFields.js
browser/extensions/formautofill/test/unit/xpcshell.ini
--- a/browser/extensions/formautofill/FormAutofillParent.jsm
+++ b/browser/extensions/formautofill/FormAutofillParent.jsm
@@ -148,17 +148,17 @@ FormAutofillParent.prototype = {
    *
    * @returns {boolean} status of form autofill feature
    */
   _getStatus() {
     if (!Services.prefs.getBoolPref(ENABLED_PREF)) {
       return false;
     }
 
-    return profileStorage.getAll().length > 0;
+    return profileStorage.addresses.getAll({noComputedFields: true}).length > 0;
   },
 
   /**
    * Set status and trigger _onStatusChanged.
    *
    * @param {boolean} newStatus The latest status we want to set for _enabled
    */
   _setStatus(newStatus) {
@@ -176,24 +176,24 @@ FormAutofillParent.prototype = {
   receiveMessage({name, data, target}) {
     switch (name) {
       case "FormAutofill:GetAddresses": {
         this._getAddresses(data, target);
         break;
       }
       case "FormAutofill:SaveAddress": {
         if (data.guid) {
-          profileStorage.update(data.guid, data.address);
+          profileStorage.addresses.update(data.guid, data.address);
         } else {
-          profileStorage.add(data.address);
+          profileStorage.addresses.add(data.address);
         }
         break;
       }
       case "FormAutofill:RemoveAddresses": {
-        data.guids.forEach(guid => profileStorage.remove(guid));
+        data.guids.forEach(guid => profileStorage.addresses.remove(guid));
         break;
       }
     }
   },
 
   /**
    * Uninitializes FormAutofillParent. This is for testing only.
    *
@@ -220,32 +220,32 @@ FormAutofillParent.prototype = {
    *         The input autocomplete property's information.
    * @param  {nsIFrameMessageManager} target
    *         Content's message manager.
    */
   _getAddresses({searchString, info}, target) {
     let addresses = [];
 
     if (info && info.fieldName) {
-      addresses = profileStorage.getByFilter({searchString, info});
+      addresses = profileStorage.addresses.getByFilter({searchString, info});
     } else {
-      addresses = profileStorage.getAll();
+      addresses = profileStorage.addresses.getAll();
     }
 
     target.sendAsyncMessage("FormAutofill:Addresses", addresses);
   },
 
   _updateSavedFieldNames() {
     if (!Services.ppmm.initialProcessData.autofillSavedFieldNames) {
       Services.ppmm.initialProcessData.autofillSavedFieldNames = new Set();
     } else {
       Services.ppmm.initialProcessData.autofillSavedFieldNames.clear();
     }
 
-    profileStorage.getAll().forEach((address) => {
+    profileStorage.addresses.getAll().forEach((address) => {
       Object.keys(address).forEach((fieldName) => {
         if (!address[fieldName]) {
           return;
         }
         Services.ppmm.initialProcessData.autofillSavedFieldNames.add(fieldName);
       });
     });
 
--- a/browser/extensions/formautofill/ProfileStorage.jsm
+++ b/browser/extensions/formautofill/ProfileStorage.jsm
@@ -8,46 +8,43 @@
  * The data is stored in JSON format, without indentation and the computed
  * fields, using UTF-8 encoding. With indentation and computed fields applied,
  * the schema would look like this:
  *
  * {
  *   version: 1,
  *   addresses: [
  *     {
- *       guid,             // 12 characters
+ *       guid,                 // 12 characters
  *
  *       // address fields
  *       given-name,
  *       additional-name,
  *       family-name,
- *       organization,     // Company
- *       street-address,   // (Multiline)
- *       address-level2,   // City/Town
- *       address-level1,   // Province (Standardized code if possible)
+ *       organization,         // Company
+ *       street-address,       // (Multiline)
+ *       address-level2,       // City/Town
+ *       address-level1,       // Province (Standardized code if possible)
  *       postal-code,
- *       country,          // ISO 3166
+ *       country,              // ISO 3166
  *       tel,
  *       email,
  *
  *       // computed fields (These fields are not stored in the file as they are
  *       // generated at runtime.)
  *       name,
  *       address-line1,
  *       address-line2,
  *       address-line3,
  *
  *       // metadata
- *       timeCreated,      // in ms
- *       timeLastUsed,     // in ms
- *       timeLastModified, // in ms
+ *       timeCreated,          // in ms
+ *       timeLastUsed,         // in ms
+ *       timeLastModified,     // in ms
  *       timesUsed
- *     },
- *     {
- *       // ...
  *     }
  *   ]
  * }
  */
 
 "use strict";
 
 // We expose a singleton from this module. Some tests may import the
@@ -66,46 +63,374 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource://gre/modules/JSONFile.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "FormAutofillNameUtils",
                                   "resource://formautofill/FormAutofillNameUtils.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "gUUIDGenerator",
                                    "@mozilla.org/uuid-generator;1",
                                    "nsIUUIDGenerator");
 
-this.log = null;
-FormAutofillUtils.defineLazyLogGetter(this, this.EXPORTED_SYMBOLS[0]);
-
 const PROFILE_JSON_FILE_NAME = "autofill-profiles.json";
 
 const SCHEMA_VERSION = 1;
 
-const VALID_FIELDS = [
+const VALID_PROFILE_FIELDS = [
   "given-name",
   "additional-name",
   "family-name",
   "organization",
   "street-address",
   "address-level2",
   "address-level1",
   "postal-code",
   "country",
   "tel",
   "email",
 ];
 
+const INTERNAL_FIELDS = [
+  "guid",
+  "timeCreated",
+  "timeLastUsed",
+  "timeLastModified",
+  "timesUsed",
+];
+
+/**
+ * Class that manipulates records in a specified collection.
+ *
+ * Note that it is responsible for converting incoming data to a consistent
+ * format in the storage. For example, computed fields will be transformed to
+ * the original fields.
+ */
+class AutofillRecords {
+  /**
+   * Creates an AutofillRecords.
+   *
+   * @param {JSONFile} store
+   *        An instance of JSONFile.
+   * @param {string} collectionName
+   *        A key of "store.data".
+   * @param {Array.<string>} validFields
+   *        A list containing non-metadata field names.
+   */
+  constructor(store, collectionName, validFields) {
+    FormAutofillUtils.defineLazyLogGetter(this, "AutofillRecords:" + collectionName);
+
+    this.VALID_FIELDS = validFields;
+
+    this._store = store;
+    this._collectionName = collectionName;
+  }
+
+  /**
+   * Gets the schema version number.
+   *
+   * @returns {number}
+   *          The current schema version number.
+   */
+  get version() {
+    return SCHEMA_VERSION;
+  }
+
+  /**
+   * Adds a new record.
+   *
+   * @param {Object} record
+   *        The new record for saving.
+   */
+  add(record) {
+    this.log.debug("add:", record);
+
+    let recordToSave = this._clone(record);
+    this._normalizeRecord(recordToSave);
+
+    let guid;
+    while (!guid || this._findByGUID(guid)) {
+      guid = gUUIDGenerator.generateUUID().toString()
+                           .replace(/[{}-]/g, "").substring(0, 12);
+    }
+    recordToSave.guid = guid;
+
+    // Metadata
+    let now = Date.now();
+    recordToSave.timeCreated = now;
+    recordToSave.timeLastModified = now;
+    recordToSave.timeLastUsed = 0;
+    recordToSave.timesUsed = 0;
+
+    this._store.data[this._collectionName].push(recordToSave);
+    this._store.saveSoon();
+
+    Services.obs.notifyObservers(null, "formautofill-storage-changed", "add");
+  }
+
+  /**
+   * Update the specified record.
+   *
+   * @param  {string} guid
+   *         Indicates which record to update.
+   * @param  {Object} record
+   *         The new record used to overwrite the old one.
+   */
+  update(guid, record) {
+    this.log.debug("update:", guid, record);
+
+    let recordFound = this._findByGUID(guid);
+    if (!recordFound) {
+      throw new Error("No matching record.");
+    }
+
+    let recordToUpdate = this._clone(record);
+    this._normalizeRecord(recordToUpdate);
+    for (let field of this.VALID_FIELDS) {
+      if (recordToUpdate[field] !== undefined) {
+        recordFound[field] = recordToUpdate[field];
+      } else {
+        delete recordFound[field];
+      }
+    }
+
+    recordFound.timeLastModified = Date.now();
+
+    this._store.saveSoon();
+
+    Services.obs.notifyObservers(null, "formautofill-storage-changed", "update");
+  }
+
+  /**
+   * Notifies the stroage of the use of the specified record, so we can update
+   * the metadata accordingly.
+   *
+   * @param  {string} guid
+   *         Indicates which record to be notified.
+   */
+  notifyUsed(guid) {
+    this.log.debug("notifyUsed:", guid);
+
+    let recordFound = this._findByGUID(guid);
+    if (!recordFound) {
+      throw new Error("No matching record.");
+    }
+
+    recordFound.timesUsed++;
+    recordFound.timeLastUsed = Date.now();
+
+    this._store.saveSoon();
+    Services.obs.notifyObservers(null, "formautofill-storage-changed", "notifyUsed");
+  }
+
+  /**
+   * Removes the specified record. No error occurs if the record isn't found.
+   *
+   * @param  {string} guid
+   *         Indicates which record to remove.
+   */
+  remove(guid) {
+    this.log.debug("remove:", guid);
+
+    this._store.data[this._collectionName] =
+      this._store.data[this._collectionName].filter(record => record.guid != guid);
+    this._store.saveSoon();
+
+    Services.obs.notifyObservers(null, "formautofill-storage-changed", "remove");
+  }
+
+  /**
+   * Returns the record with the specified GUID.
+   *
+   * @param   {string} guid
+   *          Indicates which record to retrieve.
+   * @returns {Object}
+   *          A clone of the record.
+   */
+  get(guid) {
+    this.log.debug("get:", guid);
+
+    let recordFound = this._findByGUID(guid);
+    if (!recordFound) {
+      throw new Error("No matching record.");
+    }
+
+    // The record is cloned to avoid accidental modifications from outside.
+    let clonedRecord = this._clone(recordFound);
+    this._recordReadProcessor(clonedRecord);
+    return clonedRecord;
+  }
+
+  /**
+   * Returns all records.
+   *
+   * @param   {Object} config
+   *          Specifies how data will be retrieved.
+   * @param   {boolean} config.noComputedFields
+   *          Returns raw record without those computed fields.
+   * @returns {Array.<Object>}
+   *          An array containing clones of all records.
+   */
+  getAll(config = {}) {
+    this.log.debug("getAll", config);
+
+    // Records are cloned to avoid accidental modifications from outside.
+    let clonedRecords = this._store.data[this._collectionName].map(this._clone);
+    clonedRecords.forEach(record => this._recordReadProcessor(record, config));
+    return clonedRecords;
+  }
+
+  /**
+   * Returns the filtered records based on input's information and searchString.
+   *
+   * @returns {Array.<Object>}
+   *          An array containing clones of matched record.
+   */
+  getByFilter({info, searchString}) {
+    this.log.debug("getByFilter:", info, searchString);
+
+    let lcSearchString = searchString.toLowerCase();
+    let result = this.getAll().filter(record => {
+      // Return true if string is not provided and field exists.
+      // TODO: We'll need to check if the address is for billing or shipping.
+      //       (Bug 1358941)
+      let name = record[info.fieldName];
+
+      if (!searchString) {
+        return !!name;
+      }
+
+      return name && name.toLowerCase().startsWith(lcSearchString);
+    });
+
+    this.log.debug("getByFilter:", "Returning", result.length, "result(s)");
+    return result;
+  }
+
+  _clone(record) {
+    return Object.assign({}, record);
+  }
+
+  _findByGUID(guid) {
+    let found = this._findIndexByGUID(guid);
+    return found < 0 ? undefined : this._store.data[this._collectionName][found];
+  }
+
+  _findIndexByGUID(guid) {
+    return this._store.data[this._collectionName].findIndex(record => record.guid == guid);
+  }
+
+  _normalizeRecord(record) {
+    this._recordWriteProcessor(record);
+
+    for (let key in record) {
+      if (!this.VALID_FIELDS.includes(key)) {
+        throw new Error(`"${key}" is not a valid field.`);
+      }
+      if (typeof record[key] !== "string" &&
+          typeof record[key] !== "number") {
+        throw new Error(`"${key}" contains invalid data type.`);
+      }
+    }
+  }
+
+  // An interface to be inherited.
+  _recordReadProcessor(record, config) {}
+
+  // An interface to be inherited.
+  _recordWriteProcessor(record) {}
+}
+
+class Addresses extends AutofillRecords {
+  constructor(store) {
+    super(store, "addresses", VALID_PROFILE_FIELDS);
+  }
+
+  _recordReadProcessor(profile, {noComputedFields} = {}) {
+    if (noComputedFields) {
+      return;
+    }
+
+    // Compute name
+    let name = FormAutofillNameUtils.joinNameParts({
+      given: profile["given-name"],
+      middle: profile["additional-name"],
+      family: profile["family-name"],
+    });
+    if (name) {
+      profile.name = name;
+    }
+
+    // Compute address
+    if (profile["street-address"]) {
+      let streetAddress = profile["street-address"].split("\n");
+      // TODO: we should prevent the dataloss by concatenating the rest of lines
+      //       with a locale-specific character in the future (bug 1360114).
+      for (let i = 0; i < 3; i++) {
+        if (streetAddress[i]) {
+          profile["address-line" + (i + 1)] = streetAddress[i];
+        }
+      }
+    }
+  }
+
+  _recordWriteProcessor(profile) {
+    // Normalize name
+    if (profile.name) {
+      let nameParts = FormAutofillNameUtils.splitName(profile.name);
+      if (!profile["given-name"] && nameParts.given) {
+        profile["given-name"] = nameParts.given;
+      }
+      if (!profile["additional-name"] && nameParts.middle) {
+        profile["additional-name"] = nameParts.middle;
+      }
+      if (!profile["family-name"] && nameParts.family) {
+        profile["family-name"] = nameParts.family;
+      }
+      delete profile.name;
+    }
+
+    // Normalize address
+    if (profile["address-line1"] || profile["address-line2"] ||
+        profile["address-line3"]) {
+      // Treat "street-address" as "address-line1" if it contains only one line
+      // and "address-line1" is omitted.
+      if (!profile["address-line1"] && profile["street-address"] &&
+          !profile["street-address"].includes("\n")) {
+        profile["address-line1"] = profile["street-address"];
+        delete profile["street-address"];
+      }
+
+      // Remove "address-line*" but keep the values.
+      let addressLines = [1, 2, 3].map(i => {
+        let value = profile["address-line" + i];
+        delete profile["address-line" + i];
+        return value;
+      });
+
+      // Concatenate "address-line*" if "street-address" is omitted.
+      if (!profile["street-address"]) {
+        profile["street-address"] = addressLines.join("\n");
+      }
+    }
+  }
+}
+
 function ProfileStorage(path) {
   this._path = path;
   this._initializePromise = null;
+  this.INTERNAL_FIELDS = INTERNAL_FIELDS;
 }
 
 ProfileStorage.prototype = {
-  // These fields are defined internally for each record.
-  INTERNAL_FIELDS:
-    ["guid", "timeCreated", "timeLastUsed", "timeLastModified", "timesUsed"],
+  get addresses() {
+    if (!this._addresses) {
+      this._store.ensureDataReady();
+      this._addresses = new Addresses(this._store);
+    }
+    return this._addresses;
+  },
+
   /**
    * Loads the profile data from file to memory.
    *
    * @returns {Promise}
    * @resolves When the operation finished successfully.
    * @rejects  JavaScript exception.
    */
   initialize() {
@@ -114,270 +439,16 @@ ProfileStorage.prototype = {
         path: this._path,
         dataPostProcessor: this._dataPostProcessor.bind(this),
       });
       this._initializePromise = this._store.load();
     }
     return this._initializePromise;
   },
 
-  /**
-   * Adds a new address.
-   *
-   * @param {Address} address
-   *        The new address for saving.
-   */
-  add(address) {
-    log.debug("add:", address);
-    this._store.ensureDataReady();
-
-    let addressToSave = this._clone(address);
-    this._normalizeAddress(addressToSave);
-
-    addressToSave.guid = gUUIDGenerator.generateUUID().toString()
-                                       .replace(/[{}-]/g, "").substring(0, 12);
-
-    // Metadata
-    let now = Date.now();
-    addressToSave.timeCreated = now;
-    addressToSave.timeLastModified = now;
-    addressToSave.timeLastUsed = 0;
-    addressToSave.timesUsed = 0;
-
-    this._store.data.addresses.push(addressToSave);
-
-    this._store.saveSoon();
-    Services.obs.notifyObservers(null, "formautofill-storage-changed", "add");
-  },
-
-  /**
-   * Update the specified address.
-   *
-   * @param  {string} guid
-   *         Indicates which address to update.
-   * @param  {Address} address
-   *         The new address used to overwrite the old one.
-   */
-  update(guid, address) {
-    log.debug("update:", guid, address);
-    this._store.ensureDataReady();
-
-    let addressFound = this._findByGUID(guid);
-    if (!addressFound) {
-      throw new Error("No matching record.");
-    }
-
-    let addressToUpdate = this._clone(address);
-    this._normalizeAddress(addressToUpdate);
-
-    for (let field of VALID_FIELDS) {
-      if (addressToUpdate[field] !== undefined) {
-        addressFound[field] = addressToUpdate[field];
-      } else {
-        delete addressFound[field];
-      }
-    }
-
-    addressFound.timeLastModified = Date.now();
-
-    this._store.saveSoon();
-    Services.obs.notifyObservers(null, "formautofill-storage-changed", "update");
-  },
-
-  /**
-   * Notifies the stroage of the use of the specified address, so we can update
-   * the metadata accordingly.
-   *
-   * @param  {string} guid
-   *         Indicates which address to be notified.
-   */
-  notifyUsed(guid) {
-    this._store.ensureDataReady();
-
-    let addressFound = this._findByGUID(guid);
-    if (!addressFound) {
-      throw new Error("No matching record.");
-    }
-
-    addressFound.timesUsed++;
-    addressFound.timeLastUsed = Date.now();
-
-    this._store.saveSoon();
-    Services.obs.notifyObservers(null, "formautofill-storage-changed", "notifyUsed");
-  },
-
-  /**
-   * Removes the specified address. No error occurs if the address isn't found.
-   *
-   * @param  {string} guid
-   *         Indicates which address to remove.
-   */
-  remove(guid) {
-    log.debug("remove:", guid);
-    this._store.ensureDataReady();
-
-    this._store.data.addresses =
-      this._store.data.addresses.filter(address => address.guid != guid);
-    this._store.saveSoon();
-    Services.obs.notifyObservers(null, "formautofill-storage-changed", "remove");
-  },
-
-  /**
-   * Returns the address with the specified GUID.
-   *
-   * @param   {string} guid
-   *          Indicates which address to retrieve.
-   * @returns {Address}
-   *          A clone of the address.
-   */
-  get(guid) {
-    log.debug("get:", guid);
-    this._store.ensureDataReady();
-
-    let addressFound = this._findByGUID(guid);
-    if (!addressFound) {
-      throw new Error("No matching record.");
-    }
-
-    // The record is cloned to avoid accidental modifications from outside.
-    let clonedAddress = this._clone(addressFound);
-    this._computeFields(clonedAddress);
-    return clonedAddress;
-  },
-
-  /**
-   * Returns all addresses.
-   *
-   * @returns {Array.<Address>}
-   *          An array containing clones of all addresses.
-   */
-  getAll() {
-    log.debug("getAll");
-    this._store.ensureDataReady();
-
-    // Records are cloned to avoid accidental modifications from outside.
-    let clonedAddresses = this._store.data.addresses.map(this._clone);
-    clonedAddresses.forEach(this._computeFields);
-    return clonedAddresses;
-  },
-
-  /**
-   * Returns the filtered addresses based on input's information and searchString.
-   *
-   * @returns {Array.<Address>}
-   *          An array containing clones of matched addresses.
-   */
-  getByFilter({info, searchString}) {
-    log.debug("getByFilter:", info, searchString);
-
-    let lcSearchString = searchString.toLowerCase();
-    let result = this.getAll().filter(address => {
-      // Return true if string is not provided and field exists.
-      // TODO: We'll need to check if the address is for billing or shipping.
-      //       (Bug 1358941)
-      let name = address[info.fieldName];
-
-      if (!searchString) {
-        return !!name;
-      }
-
-      return name && name.toLowerCase().startsWith(lcSearchString);
-    });
-
-    log.debug("getByFilter: Returning", result.length, "result(s)");
-    return result;
-  },
-
-  _clone(record) {
-    return Object.assign({}, record);
-  },
-
-  _findByGUID(guid) {
-    return this._store.data.addresses.find(address => address.guid == guid);
-  },
-
-  _computeFields(address) {
-    // Compute name
-    address.name = FormAutofillNameUtils.joinNameParts({
-      given: address["given-name"],
-      middle: address["additional-name"],
-      family: address["family-name"],
-    });
-
-    // Compute address
-    if (address["street-address"]) {
-      let streetAddress = address["street-address"].split("\n");
-      // TODO: we should prevent the dataloss by concatenating the rest of lines
-      //       with a locale-specific character in the future (bug 1360114).
-      for (let i = 0; i < 3; i++) {
-        if (streetAddress[i]) {
-          address["address-line" + (i + 1)] = streetAddress[i];
-        }
-      }
-    }
-  },
-
-  _normalizeAddressLines(address) {
-    if (address["address-line1"] || address["address-line2"] ||
-        address["address-line3"]) {
-      // 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"];
-      }
-
-      // Remove "address-line*" but keep the values.
-      let addressLines = [1, 2, 3].map(i => {
-        let value = address["address-line" + i];
-        delete address["address-line" + i];
-        return value;
-      });
-
-      // Concatenate "address-line*" if "street-address" is omitted.
-      if (!address["street-address"]) {
-        address["street-address"] = addressLines.join("\n");
-      }
-    }
-  },
-
-  _normalizeName(address) {
-    if (!address.name) {
-      return;
-    }
-
-    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;
-  },
-
-  _normalizeAddress(address) {
-    this._normalizeName(address);
-    this._normalizeAddressLines(address);
-
-    for (let key in address) {
-      if (!VALID_FIELDS.includes(key)) {
-        throw new Error(`"${key}" is not a valid field.`);
-      }
-      if (typeof address[key] !== "string" &&
-          typeof address[key] !== "number") {
-        throw new Error(`"${key}" contains invalid data type.`);
-      }
-    }
-  },
-
   _dataPostProcessor(data) {
     data.version = SCHEMA_VERSION;
     if (!data.addresses) {
       data.addresses = [];
     }
     return data;
   },
 
new file mode 100644
--- /dev/null
+++ b/browser/extensions/formautofill/test/unit/test_addressRecords.js
@@ -0,0 +1,286 @@
+/**
+ * Tests ProfileStorage object with addresses records.
+ */
+
+"use strict";
+
+const {ProfileStorage} = Cu.import("resource://formautofill/ProfileStorage.jsm", {});
+
+const TEST_STORE_FILE_NAME = "test-profile.json";
+
+const TEST_ADDRESS_1 = {
+  "given-name": "Timothy",
+  "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",
+  email: "timbl@w3.org",
+};
+
+const TEST_ADDRESS_2 = {
+  "street-address": "Some Address",
+  country: "US",
+};
+
+const TEST_ADDRESS_3 = {
+  "street-address": "Other Address",
+  "postal-code": "12345",
+};
+
+const TEST_ADDRESS_WITH_INVALID_FIELD = {
+  "street-address": "Another Address",
+  invalidField: "INVALID",
+};
+
+let prepareTestRecords = async function(path) {
+  let profileStorage = new ProfileStorage(path);
+  await profileStorage.initialize();
+
+  let onChanged = TestUtils.topicObserved("formautofill-storage-changed",
+                                          (subject, data) => data == "add");
+  profileStorage.addresses.add(TEST_ADDRESS_1);
+  await onChanged;
+  profileStorage.addresses.add(TEST_ADDRESS_2);
+  await profileStorage._saveImmediately();
+};
+
+let do_check_record_matches = (recordWithMeta, record) => {
+  for (let key in record) {
+    do_check_eq(recordWithMeta[key], record[key]);
+  }
+};
+
+add_task(async function test_initialize() {
+  let path = getTempFile(TEST_STORE_FILE_NAME).path;
+  let profileStorage = new ProfileStorage(path);
+  await profileStorage.initialize();
+
+  do_check_eq(profileStorage._store.data.version, 1);
+  do_check_eq(profileStorage._store.data.addresses.length, 0);
+
+  let data = profileStorage._store.data;
+  Assert.deepEqual(data.addresses, []);
+
+  await profileStorage._saveImmediately();
+
+  profileStorage = new ProfileStorage(path);
+  await profileStorage.initialize();
+
+  Assert.deepEqual(profileStorage._store.data, data);
+});
+
+add_task(async function test_getAll() {
+  let path = getTempFile(TEST_STORE_FILE_NAME).path;
+  await prepareTestRecords(path);
+
+  let profileStorage = new ProfileStorage(path);
+  await profileStorage.initialize();
+
+  let addresses = profileStorage.addresses.getAll();
+
+  do_check_eq(addresses.length, 2);
+  do_check_record_matches(addresses[0], TEST_ADDRESS_1);
+  do_check_record_matches(addresses[1], TEST_ADDRESS_2);
+
+  // Check computed fields.
+  do_check_eq(addresses[0].name, "Timothy John Berners-Lee");
+  do_check_eq(addresses[0]["address-line1"], "32 Vassar Street");
+  do_check_eq(addresses[0]["address-line2"], "MIT Room 32-G524");
+
+  // Test with noComputedFields set.
+  addresses = profileStorage.addresses.getAll({noComputedFields: true});
+  do_check_eq(addresses[0].name, undefined);
+  do_check_eq(addresses[0]["address-line1"], undefined);
+  do_check_eq(addresses[0]["address-line2"], undefined);
+
+  // Modifying output shouldn't affect the storage.
+  addresses[0].organization = "test";
+  do_check_record_matches(profileStorage.addresses.getAll()[0], TEST_ADDRESS_1);
+});
+
+add_task(async function test_get() {
+  let path = getTempFile(TEST_STORE_FILE_NAME).path;
+  await prepareTestRecords(path);
+
+  let profileStorage = new ProfileStorage(path);
+  await profileStorage.initialize();
+
+  let addresses = profileStorage.addresses.getAll();
+  let guid = addresses[0].guid;
+
+  let address = profileStorage.addresses.get(guid);
+  do_check_record_matches(address, TEST_ADDRESS_1);
+
+  // Modifying output shouldn't affect the storage.
+  address.organization = "test";
+  do_check_record_matches(profileStorage.addresses.get(guid), TEST_ADDRESS_1);
+
+  Assert.throws(() => profileStorage.addresses.get("INVALID_GUID"),
+    /No matching record\./);
+});
+
+add_task(async function test_getByFilter() {
+  let path = getTempFile(TEST_STORE_FILE_NAME).path;
+  await prepareTestRecords(path);
+
+  let profileStorage = new ProfileStorage(path);
+  await profileStorage.initialize();
+
+  let filter = {info: {fieldName: "street-address"}, searchString: "Some"};
+  let addresses = profileStorage.addresses.getByFilter(filter);
+  do_check_eq(addresses.length, 1);
+  do_check_record_matches(addresses[0], TEST_ADDRESS_2);
+
+  filter = {info: {fieldName: "country"}, searchString: "u"};
+  addresses = profileStorage.addresses.getByFilter(filter);
+  do_check_eq(addresses.length, 2);
+  do_check_record_matches(addresses[0], TEST_ADDRESS_1);
+  do_check_record_matches(addresses[1], TEST_ADDRESS_2);
+
+  filter = {info: {fieldName: "street-address"}, searchString: "test"};
+  addresses = profileStorage.addresses.getByFilter(filter);
+  do_check_eq(addresses.length, 0);
+
+  filter = {info: {fieldName: "street-address"}, searchString: ""};
+  addresses = profileStorage.addresses.getByFilter(filter);
+  do_check_eq(addresses.length, 2);
+
+  // Check if the filtering logic is free from searching special chars.
+  filter = {info: {fieldName: "street-address"}, searchString: ".*"};
+  addresses = profileStorage.addresses.getByFilter(filter);
+  do_check_eq(addresses.length, 0);
+
+  // Prevent broken while searching the property that does not exist.
+  filter = {info: {fieldName: "tel"}, searchString: "1"};
+  addresses = profileStorage.addresses.getByFilter(filter);
+  do_check_eq(addresses.length, 0);
+});
+
+add_task(async function test_add() {
+  let path = getTempFile(TEST_STORE_FILE_NAME).path;
+  await prepareTestRecords(path);
+
+  let profileStorage = new ProfileStorage(path);
+  await profileStorage.initialize();
+
+  let addresses = profileStorage.addresses.getAll();
+
+  do_check_eq(addresses.length, 2);
+
+  do_check_record_matches(addresses[0], TEST_ADDRESS_1);
+  do_check_record_matches(addresses[1], TEST_ADDRESS_2);
+
+  do_check_neq(addresses[0].guid, undefined);
+  do_check_neq(addresses[0].timeCreated, undefined);
+  do_check_eq(addresses[0].timeLastModified, addresses[0].timeCreated);
+  do_check_eq(addresses[0].timeLastUsed, 0);
+  do_check_eq(addresses[0].timesUsed, 0);
+
+  Assert.throws(() => profileStorage.addresses.add(TEST_ADDRESS_WITH_INVALID_FIELD),
+    /"invalidField" is not a valid field\./);
+});
+
+add_task(async function test_update() {
+  let path = getTempFile(TEST_STORE_FILE_NAME).path;
+  await prepareTestRecords(path);
+
+  let profileStorage = new ProfileStorage(path);
+  await profileStorage.initialize();
+
+  let addresses = profileStorage.addresses.getAll();
+  let guid = addresses[1].guid;
+  let timeLastModified = addresses[1].timeLastModified;
+
+  let onChanged = TestUtils.topicObserved("formautofill-storage-changed",
+                                          (subject, data) => data == "update");
+
+  do_check_neq(addresses[1].country, undefined);
+
+  profileStorage.addresses.update(guid, TEST_ADDRESS_3);
+  await onChanged;
+  await profileStorage._saveImmediately();
+
+  profileStorage = new ProfileStorage(path);
+  await profileStorage.initialize();
+
+  let address = profileStorage.addresses.get(guid);
+
+  do_check_eq(address.country, undefined);
+  do_check_neq(address.timeLastModified, timeLastModified);
+  do_check_record_matches(address, TEST_ADDRESS_3);
+
+  Assert.throws(
+    () => profileStorage.addresses.update("INVALID_GUID", TEST_ADDRESS_3),
+    /No matching record\./
+  );
+
+  Assert.throws(
+    () => profileStorage.addresses.update(guid, TEST_ADDRESS_WITH_INVALID_FIELD),
+    /"invalidField" is not a valid field\./
+  );
+});
+
+add_task(async function test_notifyUsed() {
+  let path = getTempFile(TEST_STORE_FILE_NAME).path;
+  await prepareTestRecords(path);
+
+  let profileStorage = new ProfileStorage(path);
+  await profileStorage.initialize();
+
+  let addresses = profileStorage.addresses.getAll();
+  let guid = addresses[1].guid;
+  let timeLastUsed = addresses[1].timeLastUsed;
+  let timesUsed = addresses[1].timesUsed;
+
+  let onChanged = TestUtils.topicObserved("formautofill-storage-changed",
+                                          (subject, data) => data == "notifyUsed");
+
+  profileStorage.addresses.notifyUsed(guid);
+  await onChanged;
+  await profileStorage._saveImmediately();
+
+  profileStorage = new ProfileStorage(path);
+  await profileStorage.initialize();
+
+  let address = profileStorage.addresses.get(guid);
+
+  do_check_eq(address.timesUsed, timesUsed + 1);
+  do_check_neq(address.timeLastUsed, timeLastUsed);
+
+  Assert.throws(() => profileStorage.addresses.notifyUsed("INVALID_GUID"),
+    /No matching record\./);
+});
+
+add_task(async function test_remove() {
+  let path = getTempFile(TEST_STORE_FILE_NAME).path;
+  await prepareTestRecords(path);
+
+  let profileStorage = new ProfileStorage(path);
+  await profileStorage.initialize();
+
+  let addresses = profileStorage.addresses.getAll();
+  let guid = addresses[1].guid;
+
+  let onChanged = TestUtils.topicObserved("formautofill-storage-changed",
+                                          (subject, data) => data == "remove");
+
+  do_check_eq(addresses.length, 2);
+
+  profileStorage.addresses.remove(guid);
+  await onChanged;
+  await profileStorage._saveImmediately();
+
+  profileStorage = new ProfileStorage(path);
+  await profileStorage.initialize();
+
+  addresses = profileStorage.addresses.getAll();
+
+  do_check_eq(addresses.length, 1);
+
+  Assert.throws(() => profileStorage.addresses.get(guid), /No matching record\./);
+});
--- a/browser/extensions/formautofill/test/unit/test_enabledStatus.js
+++ b/browser/extensions/formautofill/test/unit/test_enabledStatus.js
@@ -59,28 +59,28 @@ add_task(function* test_enabledStatus_ob
 });
 
 add_task(function* test_enabledStatus_getStatus() {
   let formAutofillParent = new FormAutofillParent();
   do_register_cleanup(function cleanup() {
     Services.prefs.clearUserPref("extensions.formautofill.addresses.enabled");
   });
 
-  sinon.stub(profileStorage, "getAll");
-  profileStorage.getAll.returns([]);
+  sinon.stub(profileStorage.addresses, "getAll");
+  profileStorage.addresses.getAll.returns([]);
 
   // pref is enabled and profile is empty.
   Services.prefs.setBoolPref("extensions.formautofill.addresses.enabled", true);
   do_check_eq(formAutofillParent._getStatus(), false);
 
   // pref is disabled and profile is empty.
   Services.prefs.setBoolPref("extensions.formautofill.addresses.enabled", false);
   do_check_eq(formAutofillParent._getStatus(), false);
 
-  profileStorage.getAll.returns(["test-profile"]);
+  profileStorage.addresses.getAll.returns(["test-profile"]);
   // pref is enabled and profile is not empty.
   Services.prefs.setBoolPref("extensions.formautofill.addresses.enabled", true);
   do_check_eq(formAutofillParent._getStatus(), true);
 
   // pref is disabled and profile is not empty.
   Services.prefs.setBoolPref("extensions.formautofill.addresses.enabled", false);
   do_check_eq(formAutofillParent._getStatus(), false);
 });
deleted file mode 100644
--- a/browser/extensions/formautofill/test/unit/test_profileStorage.js
+++ /dev/null
@@ -1,274 +0,0 @@
-/**
- * Tests ProfileStorage object.
- */
-
-"use strict";
-
-const {ProfileStorage} = Cu.import("resource://formautofill/ProfileStorage.jsm", {});
-
-const TEST_STORE_FILE_NAME = "test-profile.json";
-
-const TEST_ADDRESS_1 = {
-  "given-name": "Timothy",
-  "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",
-  email: "timbl@w3.org",
-};
-
-const TEST_ADDRESS_2 = {
-  "street-address": "Some Address",
-  country: "US",
-};
-
-const TEST_ADDRESS_3 = {
-  "street-address": "Other Address",
-  "postal-code": "12345",
-};
-
-const TEST_ADDRESS_WITH_INVALID_FIELD = {
-  "street-address": "Another Address",
-  invalidField: "INVALID",
-};
-
-let prepareTestRecords = async function(path) {
-  let profileStorage = new ProfileStorage(path);
-  await profileStorage.initialize();
-
-  let onChanged = TestUtils.topicObserved("formautofill-storage-changed",
-                                          (subject, data) => data == "add");
-  profileStorage.add(TEST_ADDRESS_1);
-  await onChanged;
-  profileStorage.add(TEST_ADDRESS_2);
-  await profileStorage._saveImmediately();
-};
-
-let do_check_record_matches = (recordWithMeta, record) => {
-  for (let key in record) {
-    do_check_eq(recordWithMeta[key], record[key]);
-  }
-};
-
-add_task(async function test_initialize() {
-  let path = getTempFile(TEST_STORE_FILE_NAME).path;
-  let profileStorage = new ProfileStorage(path);
-  await profileStorage.initialize();
-
-  do_check_eq(profileStorage._store.data.version, 1);
-  do_check_eq(profileStorage._store.data.addresses.length, 0);
-
-  let data = profileStorage._store.data;
-
-  await profileStorage._saveImmediately();
-
-  profileStorage = new ProfileStorage(path);
-  await profileStorage.initialize();
-
-  Assert.deepEqual(profileStorage._store.data, data);
-});
-
-add_task(async function test_getAll() {
-  let path = getTempFile(TEST_STORE_FILE_NAME).path;
-  await prepareTestRecords(path);
-
-  let profileStorage = new ProfileStorage(path);
-  await profileStorage.initialize();
-
-  let addresses = profileStorage.getAll();
-
-  do_check_eq(addresses.length, 2);
-  do_check_record_matches(addresses[0], TEST_ADDRESS_1);
-  do_check_record_matches(addresses[1], TEST_ADDRESS_2);
-
-  // Modifying output shouldn't affect the storage.
-  addresses[0].organization = "test";
-  do_check_record_matches(profileStorage.getAll()[0], TEST_ADDRESS_1);
-});
-
-add_task(async function test_get() {
-  let path = getTempFile(TEST_STORE_FILE_NAME).path;
-  await prepareTestRecords(path);
-
-  let profileStorage = new ProfileStorage(path);
-  await profileStorage.initialize();
-
-  let addresses = profileStorage.getAll();
-  let guid = addresses[0].guid;
-
-  let address = profileStorage.get(guid);
-  do_check_record_matches(address, TEST_ADDRESS_1);
-
-  // Modifying output shouldn't affect the storage.
-  address.organization = "test";
-  do_check_record_matches(profileStorage.get(guid), TEST_ADDRESS_1);
-
-  Assert.throws(() => profileStorage.get("INVALID_GUID"),
-    /No matching record\./);
-});
-
-add_task(async function test_getByFilter() {
-  let path = getTempFile(TEST_STORE_FILE_NAME).path;
-  await prepareTestRecords(path);
-
-  let profileStorage = new ProfileStorage(path);
-  await profileStorage.initialize();
-
-  let filter = {info: {fieldName: "street-address"}, searchString: "Some"};
-  let addresses = profileStorage.getByFilter(filter);
-  do_check_eq(addresses.length, 1);
-  do_check_record_matches(addresses[0], TEST_ADDRESS_2);
-
-  filter = {info: {fieldName: "country"}, searchString: "u"};
-  addresses = profileStorage.getByFilter(filter);
-  do_check_eq(addresses.length, 2);
-  do_check_record_matches(addresses[0], TEST_ADDRESS_1);
-  do_check_record_matches(addresses[1], TEST_ADDRESS_2);
-
-  filter = {info: {fieldName: "street-address"}, searchString: "test"};
-  addresses = profileStorage.getByFilter(filter);
-  do_check_eq(addresses.length, 0);
-
-  filter = {info: {fieldName: "street-address"}, searchString: ""};
-  addresses = profileStorage.getByFilter(filter);
-  do_check_eq(addresses.length, 2);
-
-  // Check if the filtering logic is free from searching special chars.
-  filter = {info: {fieldName: "street-address"}, searchString: ".*"};
-  addresses = profileStorage.getByFilter(filter);
-  do_check_eq(addresses.length, 0);
-
-  // Prevent broken while searching the property that does not exist.
-  filter = {info: {fieldName: "tel"}, searchString: "1"};
-  addresses = profileStorage.getByFilter(filter);
-  do_check_eq(addresses.length, 0);
-});
-
-add_task(async function test_add() {
-  let path = getTempFile(TEST_STORE_FILE_NAME).path;
-  await prepareTestRecords(path);
-
-  let profileStorage = new ProfileStorage(path);
-  await profileStorage.initialize();
-
-  let addresses = profileStorage.getAll();
-
-  do_check_eq(addresses.length, 2);
-
-  do_check_record_matches(addresses[0], TEST_ADDRESS_1);
-  do_check_record_matches(addresses[1], TEST_ADDRESS_2);
-
-  do_check_neq(addresses[0].guid, undefined);
-  do_check_neq(addresses[0].timeCreated, undefined);
-  do_check_eq(addresses[0].timeLastModified, addresses[0].timeCreated);
-  do_check_eq(addresses[0].timeLastUsed, 0);
-  do_check_eq(addresses[0].timesUsed, 0);
-
-  Assert.throws(() => profileStorage.add(TEST_ADDRESS_WITH_INVALID_FIELD),
-    /"invalidField" is not a valid field\./);
-});
-
-add_task(async function test_update() {
-  let path = getTempFile(TEST_STORE_FILE_NAME).path;
-  await prepareTestRecords(path);
-
-  let profileStorage = new ProfileStorage(path);
-  await profileStorage.initialize();
-
-  let addresses = profileStorage.getAll();
-  let guid = addresses[1].guid;
-  let timeLastModified = addresses[1].timeLastModified;
-
-  let onChanged = TestUtils.topicObserved("formautofill-storage-changed",
-                                          (subject, data) => data == "update");
-
-  do_check_neq(addresses[1].country, undefined);
-
-  profileStorage.update(guid, TEST_ADDRESS_3);
-  await onChanged;
-  await profileStorage._saveImmediately();
-
-  profileStorage = new ProfileStorage(path);
-  await profileStorage.initialize();
-
-  let address = profileStorage.get(guid);
-
-  do_check_eq(address.country, undefined);
-  do_check_neq(address.timeLastModified, timeLastModified);
-  do_check_record_matches(address, TEST_ADDRESS_3);
-
-  Assert.throws(
-    () => profileStorage.update("INVALID_GUID", TEST_ADDRESS_3),
-    /No matching record\./
-  );
-
-  Assert.throws(
-    () => profileStorage.update(guid, TEST_ADDRESS_WITH_INVALID_FIELD),
-    /"invalidField" is not a valid field\./
-  );
-});
-
-add_task(async function test_notifyUsed() {
-  let path = getTempFile(TEST_STORE_FILE_NAME).path;
-  await prepareTestRecords(path);
-
-  let profileStorage = new ProfileStorage(path);
-  await profileStorage.initialize();
-
-  let addresses = profileStorage.getAll();
-  let guid = addresses[1].guid;
-  let timeLastUsed = addresses[1].timeLastUsed;
-  let timesUsed = addresses[1].timesUsed;
-
-  let onChanged = TestUtils.topicObserved("formautofill-storage-changed",
-                                          (subject, data) => data == "notifyUsed");
-
-  profileStorage.notifyUsed(guid);
-  await onChanged;
-  await profileStorage._saveImmediately();
-
-  profileStorage = new ProfileStorage(path);
-  await profileStorage.initialize();
-
-  let address = profileStorage.get(guid);
-
-  do_check_eq(address.timesUsed, timesUsed + 1);
-  do_check_neq(address.timeLastUsed, timeLastUsed);
-
-  Assert.throws(() => profileStorage.notifyUsed("INVALID_GUID"),
-    /No matching record\./);
-});
-
-add_task(async function test_remove() {
-  let path = getTempFile(TEST_STORE_FILE_NAME).path;
-  await prepareTestRecords(path);
-
-  let profileStorage = new ProfileStorage(path);
-  await profileStorage.initialize();
-
-  let addresses = profileStorage.getAll();
-  let guid = addresses[1].guid;
-
-  let onChanged = TestUtils.topicObserved("formautofill-storage-changed",
-                                          (subject, data) => data == "remove");
-
-  do_check_eq(addresses.length, 2);
-
-  profileStorage.remove(guid);
-  await onChanged;
-  await profileStorage._saveImmediately();
-
-  profileStorage = new ProfileStorage(path);
-  await profileStorage.initialize();
-
-  addresses = profileStorage.getAll();
-
-  do_check_eq(addresses.length, 1);
-
-  Assert.throws(() => profileStorage.get(guid), /No matching record\./);
-});
--- a/browser/extensions/formautofill/test/unit/test_savedFieldNames.js
+++ b/browser/extensions/formautofill/test/unit/test_savedFieldNames.js
@@ -40,18 +40,18 @@ add_task(async function test_profileSave
 
 add_task(async function test_profileSavedFieldNames_update() {
   let formAutofillParent = new FormAutofillParent();
   await formAutofillParent.init();
   do_register_cleanup(function cleanup() {
     Services.prefs.clearUserPref("extensions.formautofill.addresses.enabled");
   });
 
-  sinon.stub(profileStorage, "getAll");
-  profileStorage.getAll.returns([]);
+  sinon.stub(profileStorage.addresses, "getAll");
+  profileStorage.addresses.getAll.returns([]);
 
   // The set is empty if there's no profile in the store.
   formAutofillParent._updateSavedFieldNames();
   do_check_eq(Services.ppmm.initialProcessData.autofillSavedFieldNames.size, 0);
 
   // 2 profiles with 4 valid fields.
   let fakeStorage = [{
     guid: "test-guid-1",
@@ -69,17 +69,17 @@ add_task(async function test_profileSave
     "street-address": "331 E. Evelyn Avenue",
     tel: "1-650-903-0800",
     country: "US",
     timeCreated: 0,
     timeLastUsed: 0,
     timeLastModified: 0,
     timesUsed: 0,
   }];
-  profileStorage.getAll.returns(fakeStorage);
+  profileStorage.addresses.getAll.returns(fakeStorage);
   formAutofillParent._updateSavedFieldNames();
 
   let autofillSavedFieldNames = Services.ppmm.initialProcessData.autofillSavedFieldNames;
   do_check_eq(autofillSavedFieldNames.size, 4);
   do_check_eq(autofillSavedFieldNames.has("organization"), true);
   do_check_eq(autofillSavedFieldNames.has("street-address"), true);
   do_check_eq(autofillSavedFieldNames.has("tel"), true);
   do_check_eq(autofillSavedFieldNames.has("email"), false);
--- a/browser/extensions/formautofill/test/unit/test_transformFields.js
+++ b/browser/extensions/formautofill/test/unit/test_transformFields.js
@@ -3,17 +3,17 @@
  */
 
 "use strict";
 
 const {ProfileStorage} = Cu.import("resource://formautofill/ProfileStorage.jsm", {});
 
 const TEST_STORE_FILE_NAME = "test-profile.json";
 
-const COMPUTE_TESTCASES = [
+const ADDRESS_COMPUTE_TESTCASES = [
   // Empty
   {
     description: "Empty address",
     address: {
     },
     expectedResult: {
     },
   },
@@ -78,17 +78,17 @@ const COMPUTE_TESTCASES = [
       "street-address": "line1\nline2\nline3\nline4",
       "address-line1": "line1",
       "address-line2": "line2",
       "address-line3": "line3",
     },
   },
 ];
 
-const NORMALIZE_TESTCASES = [
+const ADDRESS_NORMALIZE_TESTCASES = [
   // Empty
   {
     description: "Empty address",
     address: {
     },
     expectedResult: {
     },
   },
@@ -181,47 +181,47 @@ const NORMALIZE_TESTCASES = [
 ];
 
 let do_check_record_matches = (expectedRecord, record) => {
   for (let key in expectedRecord) {
     do_check_eq(expectedRecord[key], record[key] || "");
   }
 };
 
-add_task(async function test_computeFields() {
+add_task(async function test_computeAddressFields() {
   let path = getTempFile(TEST_STORE_FILE_NAME).path;
 
   let profileStorage = new ProfileStorage(path);
   await profileStorage.initialize();
 
-  COMPUTE_TESTCASES.forEach(testcase => profileStorage.add(testcase.address));
+  ADDRESS_COMPUTE_TESTCASES.forEach(testcase => profileStorage.addresses.add(testcase.address));
   await profileStorage._saveImmediately();
 
   profileStorage = new ProfileStorage(path);
   await profileStorage.initialize();
 
-  let addresses = profileStorage.getAll();
+  let addresses = profileStorage.addresses.getAll();
 
   for (let i in addresses) {
-    do_print("Verify testcase: " + COMPUTE_TESTCASES[i].description);
-    do_check_record_matches(COMPUTE_TESTCASES[i].expectedResult, addresses[i]);
+    do_print("Verify testcase: " + ADDRESS_COMPUTE_TESTCASES[i].description);
+    do_check_record_matches(ADDRESS_COMPUTE_TESTCASES[i].expectedResult, addresses[i]);
   }
 });
 
-add_task(async function test_normalizeFields() {
+add_task(async function test_normalizeAddressFields() {
   let path = getTempFile(TEST_STORE_FILE_NAME).path;
 
   let profileStorage = new ProfileStorage(path);
   await profileStorage.initialize();
 
-  NORMALIZE_TESTCASES.forEach(testcase => profileStorage.add(testcase.address));
+  ADDRESS_NORMALIZE_TESTCASES.forEach(testcase => profileStorage.addresses.add(testcase.address));
   await profileStorage._saveImmediately();
 
   profileStorage = new ProfileStorage(path);
   await profileStorage.initialize();
 
-  let addresses = profileStorage.getAll();
+  let addresses = profileStorage.addresses.getAll();
 
   for (let i in addresses) {
-    do_print("Verify testcase: " + NORMALIZE_TESTCASES[i].description);
-    do_check_record_matches(NORMALIZE_TESTCASES[i].expectedResult, addresses[i]);
+    do_print("Verify testcase: " + ADDRESS_NORMALIZE_TESTCASES[i].description);
+    do_check_record_matches(ADDRESS_NORMALIZE_TESTCASES[i].expectedResult, addresses[i]);
   }
 });
--- a/browser/extensions/formautofill/test/unit/xpcshell.ini
+++ b/browser/extensions/formautofill/test/unit/xpcshell.ini
@@ -11,21 +11,21 @@ support-files =
 [heuristics/third_party/test_HomeDepot.js]
 [heuristics/third_party/test_Macys.js]
 [heuristics/third_party/test_NewEgg.js]
 [heuristics/third_party/test_OfficeDepot.js]
 [heuristics/third_party/test_QVC.js]
 [heuristics/third_party/test_Sears.js]
 [heuristics/third_party/test_Staples.js]
 [heuristics/third_party/test_Walmart.js]
+[test_addressRecords.js]
 [test_autofillFormFields.js]
 [test_collectFormFields.js]
 [test_enabledStatus.js]
 [test_findLabelElements.js]
 [test_getFormInputDetails.js]
 [test_isCJKName.js]
 [test_markAsAutofillField.js]
 [test_nameUtils.js]
 [test_onFormSubmitted.js]
 [test_profileAutocompleteResult.js]
-[test_profileStorage.js]
 [test_savedFieldNames.js]
 [test_transformFields.js]