Bug 1300988 - Part 2: XPCShell test for collectFormFields/autofillFormFields. r=MattN draft
authorSteve Chung <schung@mozilla.com>
Thu, 22 Sep 2016 16:38:08 +0800
changeset 416465 b76b32989adebc2676f982657c2250b82cb671db
parent 415885 ff33eb8c27d3bd1abf9dd2ad59b056ee1c9979e9
child 531869 40e30b28b4938cdbf4f6d9673614f4d7c77cd46f
push id30160
push userschung@mozilla.com
push dateThu, 22 Sep 2016 08:41:57 +0000
reviewersMattN
bugs1300988
milestone52.0a1
Bug 1300988 - Part 2: XPCShell test for collectFormFields/autofillFormFields. r=MattN MozReview-Commit-ID: AsgAHIYRKkp
browser/extensions/formautofill/content/FormAutofillContent.js
browser/extensions/formautofill/jar.mn
browser/extensions/formautofill/moz.build
browser/extensions/formautofill/test/unit/head.js
browser/extensions/formautofill/test/unit/test_formAutoFillContent.js
browser/extensions/formautofill/test/unit/xpcshell.ini
--- a/browser/extensions/formautofill/content/FormAutofillContent.js
+++ b/browser/extensions/formautofill/content/FormAutofillContent.js
@@ -3,18 +3,16 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /*
  * Implements a service used by DOM content to request Form Autofill.
  */
 
 "use strict";
 
-const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
-
 /**
  * Handles requestAutocomplete for a DOM Form element.
  */
 function FormAutoFillHandler(aForm) {
   this.form = aForm;
   this.fieldDetails = [];
 }
 
@@ -49,22 +47,23 @@ FormAutoFillHandler.prototype = {
    */
   collectFormFields: function () {
     let autofillData = {
       sections: [],
     };
 
     for (let element of this.form.elements) {
       // Query the interface and exclude elements that cannot be autocompleted.
-      if (!(element instanceof Ci.nsIDOMHTMLInputElement)) {
+      if (!(element instanceof Components.interfaces.nsIDOMHTMLInputElement)) {
         continue;
       }
 
       // Exclude elements to which no autocomplete field has been assigned.
       let info = element.getAutocompleteInfo();
+
       if (!info.fieldName || ["on", "off"].indexOf(info.fieldName) != -1) {
         continue;
       }
 
       // Store the association between the field metadata and the element.
       if (this.fieldDetails.some(f => f.section == info.section &&
                                       f.addressType == info.addressType &&
                                       f.contactType == info.contactType &&
@@ -131,16 +130,18 @@ FormAutoFillHandler.prototype = {
    *          ],
    *        }
    */
   autofillFormFields: function (aAutofillResult) {
     for (let field of aAutofillResult.fields) {
       // Get the field details, if it was processed by the user interface.
       let fieldDetail = this.fieldDetails[field.index];
 
-      if (!fieldDetail) {
+      // Avoid the invalid value set
+      if (!fieldDetail || !field.value) {
         continue;
       }
 
       fieldDetail.element.value = field.value;
     }
   },
 };
+
--- a/browser/extensions/formautofill/jar.mn
+++ b/browser/extensions/formautofill/jar.mn
@@ -1,7 +1,7 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 [features/formautofill@mozilla.org] chrome.jar:
-% resource formautofill %content/
+% content formautofill %content/
  content/ (content/*)
--- a/browser/extensions/formautofill/moz.build
+++ b/browser/extensions/formautofill/moz.build
@@ -12,10 +12,11 @@ FINAL_TARGET_FILES.features['formautofil
 ]
 
 FINAL_TARGET_PP_FILES.features['formautofill@mozilla.org'] += [
   'install.rdf.in'
 ]
 
 BROWSER_CHROME_MANIFESTS += ['test/browser/browser.ini']
 
+XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
+
 JAR_MANIFESTS += ['jar.mn']
-
new file mode 100644
--- /dev/null
+++ b/browser/extensions/formautofill/test/unit/head.js
@@ -0,0 +1,73 @@
+/**
+ * Provides infrastructure for automated login components tests.
+ */
+
+"use strict";
+
+////////////////////////////////////////////////////////////////////////////////
+//// Globals
+
+let { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Services",
+                                  "resource://gre/modules/Services.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+                                  "resource://gre/modules/Promise.jsm");
+
+////////////////////////////////////////////////////////////////////////////////
+//// Global helpers
+
+const MockDocument = {
+  /**
+   * Create a document for the given URL containing the given HTML with the ownerDocument of all <form>s having a mocked location.
+   */
+  createTestDocument(aDocumentURL, aContent = "<form>", aType = "text/html") {
+    let parser = Cc["@mozilla.org/xmlextras/domparser;1"].
+                 createInstance(Ci.nsIDOMParser);
+    parser.init();
+    let parsedDoc = parser.parseFromString(aContent, aType);
+
+    for (let element of parsedDoc.forms) {
+      this.mockOwnerDocumentProperty(element, parsedDoc, aDocumentURL);
+    }
+    return parsedDoc;
+  },
+
+  mockOwnerDocumentProperty(aElement, aDoc, aURL) {
+    // Mock the document.location object so we can unit test without a frame. We use a proxy
+    // instead of just assigning to the property since it's not configurable or writable.
+    let document = new Proxy(aDoc, {
+      get(target, property, receiver) {
+        // document.location is normally null when a document is outside of a "browsing context".
+        // See https://html.spec.whatwg.org/#the-location-interface
+        if (property == "location") {
+          return new URL(aURL);
+        }
+        return target[property];
+      },
+    });
+
+    // Assign element.ownerDocument to the proxy so document.location works.
+    Object.defineProperty(aElement, "ownerDocument", {
+      value: document,
+    });
+  },
+
+};
+
+//// Initialization functions common to all tests
+
+add_task(function* test_common_initialize()
+{
+  Services.prefs.setBoolPref("dom.forms.autocomplete.experimental", true);
+  Services.prefs.setBoolPref("dom.forms.requestAutocomplete", true);
+
+  // Clean up after every test.
+  do_register_cleanup(() => {
+    Services.prefs.setBoolPref("dom.forms.autocomplete.experimental", false);
+    Services.prefs.setBoolPref("dom.forms.requestAutocomplete", false);
+  });
+});
new file mode 100644
--- /dev/null
+++ b/browser/extensions/formautofill/test/unit/test_formAutoFillContent.js
@@ -0,0 +1,229 @@
+/*
+ * Test for form auto fill content helper functions.
+ */
+
+"use strict";
+
+load("FormAutofillContent.js");
+
+const TESTCASES = [
+  {
+    description: "Form without autocomplete property",
+    document: `<form><input id="given-name"><input id="family-name">
+               <input id="street-addr"><input id="city"><input id="country">
+               <input id='email'><input id="tel"></form>`,
+    returnedFormat: {"sections":[]},
+    fieldDetails: [],
+    profileData:{ fields: [] },
+    expectedResult: {
+      "given-name": "",
+      "family-name": "",
+      "street-addr": "",
+      "city": "",
+      "country": "",
+      "email": "",
+      "tel": "",
+    },
+  },
+  {
+    description: "Form with autocomplete properties and 1 token",
+    document: `<form><input id="given-name" autocomplete="given-name">
+               <input id="family-name" autocomplete="family-name">
+               <input id="street-addr" autocomplete="street-address">
+               <input id="city" autocomplete="address-level2">
+               <input id="country" autocomplete="country">
+               <input id='email' autocomplete="email">
+               <input id="tel" autocomplete="tel"></form>`,
+    returnedFormat: {
+      "sections":[
+        {
+          "name":"",
+          "addressSections": [{
+            "addressType":"",
+            "fields":[
+              {"fieldName":"given-name","contactType":"","index":0},
+              {"fieldName":"family-name","contactType":"","index":1},
+              {"fieldName":"street-address","contactType":"","index":2},
+              {"fieldName":"address-level2","contactType":"","index":3},
+              {"fieldName":"country","contactType":"","index":4},
+              {"fieldName":"email","contactType":"","index":5},
+              {"fieldName":"tel","contactType":"","index":6},
+            ]
+          }]
+        }
+      ]
+    },
+    fieldDetails: [
+      {"section":"","addressType":"","contactType":"","fieldName":"given-name","element":{}},
+      {"section":"","addressType":"","contactType":"","fieldName":"family-name","element":{}},
+      {"section":"","addressType":"","contactType":"","fieldName":"street-address","element":{}},
+      {"section":"","addressType":"","contactType":"","fieldName":"address-level2","element":{}},
+      {"section":"","addressType":"","contactType":"","fieldName":"country","element":{}},
+      {"section":"","addressType":"","contactType":"","fieldName":"email","element":{}},
+      {"section":"","addressType":"","contactType":"","fieldName":"tel","element":{}}
+    ],
+    profileData:{
+      "fields":[
+        {"fieldName":"given-name","contactType":"","index":0,"value":"foo"},
+        {"fieldName":"family-name","contactType":"","index":1,"value":"bar"},
+        {"fieldName":"street-address","contactType":"","index":2,"value":"2 Harrison St"},
+        {"fieldName":"address-level2","contactType":"","index":3,"value":"San Francisco"},
+        {"fieldName":"country","contactType":"","index":4,"value":"US"},
+        {"fieldName":"email","contactType":"","index":5,"value":"foo@mozilla.com"},
+        {"fieldName":"tel","contactType":"","index":6,"value":"1234567"},
+      ]
+    },
+    expectedResult: {
+      "given-name": "foo",
+      "family-name": "bar",
+      "street-addr": "2 Harrison St",
+      "city": "San Francisco",
+      "country": "US",
+      "email": "foo@mozilla.com",
+      "tel": "1234567",
+    },
+  },
+  {
+    description: "Form with autocomplete properties and 2 tokens",
+    document: `<form><input id="given-name" autocomplete="shipping given-name">
+               <input id="family-name" autocomplete="shipping family-name">
+               <input id="street-addr" autocomplete="shipping street-address">
+               <input id="city" autocomplete="shipping address-level2">
+               <input id="country" autocomplete="shipping country">
+               <input id='email' autocomplete="shipping email">
+               <input id="tel" autocomplete="shipping tel"></form>`,
+    returnedFormat: {
+      "sections":[
+        {
+          "name":"",
+          "addressSections": [{
+            "addressType":"shipping",
+            "fields":[
+              {"fieldName":"given-name","contactType":"","index":0},
+              {"fieldName":"family-name","contactType":"","index":1},
+              {"fieldName":"street-address","contactType":"","index":2},
+              {"fieldName":"address-level2","contactType":"","index":3},
+              {"fieldName":"country","contactType":"","index":4},
+              {"fieldName":"email","contactType":"","index":5},
+              {"fieldName":"tel","contactType":"","index":6},
+            ]
+          }]
+        }
+      ]
+    },
+    fieldDetails: [
+      {"section":"","addressType":"shipping","contactType":"","fieldName":"given-name","element":{}},
+      {"section":"","addressType":"shipping","contactType":"","fieldName":"family-name","element":{}},
+      {"section":"","addressType":"shipping","contactType":"","fieldName":"street-address","element":{}},
+      {"section":"","addressType":"shipping","contactType":"","fieldName":"address-level2","element":{}},
+      {"section":"","addressType":"shipping","contactType":"","fieldName":"country","element":{}},
+      {"section":"","addressType":"shipping","contactType":"","fieldName":"email","element":{}},
+      {"section":"","addressType":"shipping","contactType":"","fieldName":"tel","element":{}}
+    ],
+    profileData:{
+      "fields":[
+        {"fieldName":"given-name","contactType":"","index":0,"value":"foo"},
+        {"fieldName":"family-name","contactType":"","index":1,"value":"bar"},
+        {"fieldName":"street-address","contactType":"","index":2,"value":"2 Harrison St"},
+        {"fieldName":"address-level2","contactType":"","index":3,"value":"San Francisco"},
+        {"fieldName":"country","contactType":"","index":4,"value":"US"},
+        {"fieldName":"email","contactType":"","index":5,"value":"foo@mozilla.com"},
+        {"fieldName":"tel","contactType":"","index":6,"value":"1234567"},
+      ]
+    },
+    expectedResult: {
+      "given-name": "foo",
+      "family-name": "bar",
+      "street-addr": "2 Harrison St",
+      "city": "San Francisco",
+      "country": "US",
+      "email": "foo@mozilla.com",
+      "tel": "1234567",
+    },
+  },
+  {
+    description: "Form with autocomplete properties and profile is partly matched",
+    document: `<form><input id="given-name" autocomplete="shipping given-name">
+               <input id="family-name" autocomplete="shipping family-name">
+               <input id="street-addr" autocomplete="shipping street-address">
+               <input id="city" autocomplete="shipping address-level2">
+               <input id="country" autocomplete="shipping country">
+               <input id='email' autocomplete="shipping email">
+               <input id="tel" autocomplete="shipping tel"></form>`,
+    returnedFormat: {
+      "sections":[
+        {
+          "name":"",
+          "addressSections": [{
+            "addressType":"shipping",
+            "fields":[
+              {"fieldName":"given-name","contactType":"","index":0},
+              {"fieldName":"family-name","contactType":"","index":1},
+              {"fieldName":"street-address","contactType":"","index":2},
+              {"fieldName":"address-level2","contactType":"","index":3},
+              {"fieldName":"country","contactType":"","index":4},
+              {"fieldName":"email","contactType":"","index":5},
+              {"fieldName":"tel","contactType":"","index":6},
+            ]
+          }]
+        }
+      ]
+    },
+    fieldDetails: [
+      {"section":"","addressType":"shipping","contactType":"","fieldName":"given-name","element":{}},
+      {"section":"","addressType":"shipping","contactType":"","fieldName":"family-name","element":{}},
+      {"section":"","addressType":"shipping","contactType":"","fieldName":"street-address","element":{}},
+      {"section":"","addressType":"shipping","contactType":"","fieldName":"address-level2","element":{}},
+      {"section":"","addressType":"shipping","contactType":"","fieldName":"country","element":{}},
+      {"section":"","addressType":"shipping","contactType":"","fieldName":"email","element":{}},
+      {"section":"","addressType":"shipping","contactType":"","fieldName":"tel","element":{}}
+    ],
+    profileData:{
+      "fields":[
+        {"fieldName":"given-name","contactType":"","index":0,"value":"foo"},
+        {"fieldName":"family-name","contactType":"","index":1,"value":"bar"},
+        {"fieldName":"street-address","contactType":"","index":2,"value":"2 Harrison St"},
+        {"fieldName":"address-level2","contactType":"","index":3,"value":"San Francisco"},
+        {"fieldName":"country","contactType":"","index":4,"value":"US"},
+        {"fieldName":"email","contactType":"","index":5},
+        {"fieldName":"tel","contactType":"","index":6},
+      ]
+    },
+    expectedResult: {
+      "given-name": "foo",
+      "family-name": "bar",
+      "street-addr": "2 Harrison St",
+      "city": "San Francisco",
+      "country": "US",
+      "email": "",
+      "tel": "",
+    },
+  },
+];
+
+for (let tc of TESTCASES) {
+
+  (function() {
+    let testcase = tc;
+    add_task(function*() {
+      do_print("Starting testcase: " + testcase.description);
+
+      let doc = MockDocument.createTestDocument("http://localhost:8080/test/",
+                                                     testcase.document);
+      let form = doc.querySelector("form");
+      let handler = new FormAutoFillHandler(doc.querySelector('form'));
+
+      Assert.deepEqual(handler.collectFormFields(), testcase.returnedFormat,
+                         "Check the format of form autofill were returned correctly");
+
+      Assert.deepEqual(handler.fieldDetails, testcase.fieldDetails,
+                         "Check the fieldDetails were set correctly");
+
+      handler.autofillFormFields(testcase.profileData);
+      for (let id in testcase.expectedResult) {
+        Assert.equal(doc.getElementById(id).value, testcase.expectedResult[id],
+                    "Check the " + id + " fields were filled with correct data");
+      }
+    });
+  })();
+}
new file mode 100644
--- /dev/null
+++ b/browser/extensions/formautofill/test/unit/xpcshell.ini
@@ -0,0 +1,7 @@
+[DEFAULT]
+head = head.js
+tail =
+support-files = ../../content/FormAutofillContent.js
+firefox-appdir = browser
+
+[test_formAutoFillContent.js]