Bug 1173751 - Add unit tests for search suggestions in awesomebar. r=mak
authorDrew Willcoxon <adw@mozilla.com>
Tue, 16 Jun 2015 12:51:09 -0700
changeset 267257 31fbd3b1027c89a958d90c448f3d328ccb983864
parent 267256 1a21a5d0e9da9e8508256e97fea93a2efd83c96a
child 267258 d2eb65fdebb3a9808bcfd193c7844727b7341404
push id4932
push userjlund@mozilla.com
push dateMon, 10 Aug 2015 18:23:06 +0000
treeherdermozilla-esr52@6dd5a4f5f745 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmak
bugs1173751
milestone41.0a1
Bug 1173751 - Add unit tests for search suggestions in awesomebar. r=mak
toolkit/components/places/tests/unifiedcomplete/data/engine-suggestions.xml
toolkit/components/places/tests/unifiedcomplete/head_autocomplete.js
toolkit/components/places/tests/unifiedcomplete/test_searchEngine_host.js
toolkit/components/places/tests/unifiedcomplete/test_searchSuggestions.js
toolkit/components/places/tests/unifiedcomplete/xpcshell.ini
new file mode 100644
--- /dev/null
+++ b/toolkit/components/places/tests/unifiedcomplete/data/engine-suggestions.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>engine-suggestions.xml</ShortName>
+<Url type="application/x-suggestions+json"
+     method="GET"
+     template="http://localhost:9000/suggest?{searchTerms}"/>
+<Url type="text/html"
+     method="GET"
+     template="http://localhost:9000/search"
+     rel="searchform"/>
+</SearchPlugin>
--- a/toolkit/components/places/tests/unifiedcomplete/head_autocomplete.js
+++ b/toolkit/components/places/tests/unifiedcomplete/head_autocomplete.js
@@ -3,16 +3,17 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 const Ci = Components.interfaces;
 const Cc = Components.classes;
 const Cr = Components.results;
 const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://testing-common/httpd.js");
 
 // Import common head.
 {
   let commonFile = do_get_file("../head_common.js", false);
   let uri = Services.io.newFileURI(commonFile);
   Services.scriptloader.loadSubScript(uri.spec, this);
 }
 
@@ -24,17 +25,24 @@ function run_test() {
   run_next_test();
 }
 
 function* cleanup() {
   Services.prefs.clearUserPref("browser.urlbar.autocomplete.enabled");
   Services.prefs.clearUserPref("browser.urlbar.autoFill");
   Services.prefs.clearUserPref("browser.urlbar.autoFill.typed");
   Services.prefs.clearUserPref("browser.urlbar.autoFill.searchEngines");
-  for (let type of ["history", "bookmark", "history.onlyTyped", "openpage"]) {
+  let suggestPrefs = [
+    "history",
+    "bookmark",
+    "history.onlyTyped",
+    "openpage",
+    "searches",
+  ];
+  for (let type of suggestPrefs) {
     Services.prefs.clearUserPref("browser.urlbar.suggest." + type);
   }
   yield PlacesUtils.bookmarks.eraseEverything();
   yield PlacesTestUtils.clearHistory();
 }
 do_register_cleanup(cleanup);
 
 /**
@@ -217,17 +225,17 @@ function* check_autocomplete(test) {
           // Make it undefined so we don't process it again
           matches[j] = undefined;
           break;
         }
       }
 
       // We didn't hit the break, so we must have not found it
       if (j == matches.length)
-        do_throw(`Didn't find the current result ("${result.value}", "${result.comment}") in matches`);
+        do_throw(`Didn't find the current result ("${result.value}", "${result.comment}") in matches`); //' (Emacs syntax highlighting fix)
     }
 
     Assert.equal(controller.matchCount, matches.length,
                  "Got as many results as expected");
 
     // If we expect results, make sure we got matches.
     do_check_eq(controller.searchStatus, matches.length ?
                 Ci.nsIAutoCompleteController.STATUS_COMPLETE_MATCH :
@@ -388,22 +396,66 @@ function setFaviconForHref(href, iconHre
       NetUtil.newURI(iconHref),
       true,
       PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
       resolve
     );
   });
 }
 
+function makeTestServer(port=-1) {
+  let httpServer = new HttpServer();
+  httpServer.start(port);
+  do_register_cleanup(() => httpServer.stop(() => {}));
+  return httpServer;
+}
+
+function* addTestEngine(basename, httpServer=undefined) {
+  httpServer = httpServer || makeTestServer();
+  httpServer.registerDirectory("/", do_get_cwd());
+  let dataUrl =
+    "http://localhost:" + httpServer.identity.primaryPort + "/data/";
+
+  do_print("Adding engine: " + basename);
+  return yield new Promise(resolve => {
+    Services.obs.addObserver(function obs(subject, topic, data) {
+      let engine = subject.QueryInterface(Ci.nsISearchEngine);
+      do_print("Observed " + data + " for " + engine.name);
+      if (data != "engine-added" || engine.name != basename) {
+        return;
+      }
+
+      Services.obs.removeObserver(obs, "browser-search-engine-modified");
+      do_register_cleanup(() => Services.search.removeEngine(engine));
+      resolve(engine);
+    }, "browser-search-engine-modified", false);
+
+    do_print("Adding engine from URL: " + dataUrl + basename);
+    Services.search.addEngine(dataUrl + basename,
+                              Ci.nsISearchEngine.DATA_XML, null, false);
+  });
+}
+
 // Ensure we have a default search engine and the keyword.enabled preference
 // set.
 add_task(function ensure_search_engine() {
   // keyword.enabled is necessary for the tests to see keyword searches.
   Services.prefs.setBoolPref("keyword.enabled", true);
 
+  // Initialize the search service, but first set this geo IP pref to a dummy
+  // string.  When the search service is initialized, it contacts the URI named
+  // in this pref, which breaks the test since outside connections aren't
+  // allowed.
+  let geoPref = "browser.search.geoip.url";
+  Services.prefs.setCharPref(geoPref, "");
+  do_register_cleanup(() => Services.prefs.clearUserPref(geoPref));
+  yield new Promise(resolve => {
+    Services.search.init(resolve);
+  });
+
   // Remove any existing engines before adding ours.
   for (let engine of Services.search.getEngines()) {
     Services.search.removeEngine(engine);
   }
   Services.search.addEngineWithDetails("MozSearch", "", "", "", "GET",
                                        "http://s.example.com/search");
   let engine = Services.search.getEngineByName("MozSearch");
   Services.search.currentEngine = engine;
--- a/toolkit/components/places/tests/unifiedcomplete/test_searchEngine_host.js
+++ b/toolkit/components/places/tests/unifiedcomplete/test_searchEngine_host.js
@@ -1,47 +1,11 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
-Cu.import("resource://testing-common/httpd.js");
-
-function* addTestEngines(items) {
-  let httpServer = new HttpServer();
-  httpServer.start(-1);
-  httpServer.registerDirectory("/", do_get_cwd());
-  let gDataUrl = "http://localhost:" + httpServer.identity.primaryPort + "/data/";
-  do_register_cleanup(() => httpServer.stop(() => {}));
-
-  let engines = [];
-
-  for (let item of items) {
-    do_print("Adding engine: " + item);
-    yield new Promise(resolve => {
-      Services.obs.addObserver(function obs(subject, topic, data) {
-        let engine = subject.QueryInterface(Ci.nsISearchEngine);
-        do_print("Observed " + data + " for " + engine.name);
-        if (data != "engine-added" || engine.name != item) {
-          return;
-        }
-
-        Services.obs.removeObserver(obs, "browser-search-engine-modified");
-        engines.push(engine);
-        resolve();
-      }, "browser-search-engine-modified", false);
-
-      do_print("`Adding engine from URL: " + gDataUrl + item);
-      Services.search.addEngine(gDataUrl + item,
-                                Ci.nsISearchEngine.DATA_XML, null, false);
-    });
-  }
-
-  return engines;
-}
-
-
 add_task(function* test_searchEngine_autoFill() {
   Services.prefs.setBoolPref("browser.urlbar.autoFill.searchEngines", true);
   Services.search.addEngineWithDetails("MySearchEngine", "", "", "",
                                        "GET", "http://my.search.com/");
   let engine = Services.search.getEngineByName("MySearchEngine");
   do_register_cleanup(() => Services.search.removeEngine(engine));
 
   // Add an uri that matches the search string with high frecency.
@@ -62,18 +26,17 @@ add_task(function* test_searchEngine_aut
     completed: "http://my.search.com"
   });
 
   yield cleanup();
 });
 
 add_task(function* test_searchEngine_noautoFill() {
   let engineName = "engine-rel-searchform.xml";
-  let [engine] = yield addTestEngines([engineName]);
-  do_register_cleanup(() => Services.search.removeEngine(engine));
+  let engine = yield addTestEngine(engineName);
   equal(engine.searchForm, "http://example.com/?search");
 
   Services.prefs.setBoolPref("browser.urlbar.autoFill.typed", false);
   yield PlacesTestUtils.addVisits(NetUtil.newURI("http://example.com/my/"));
 
   do_print("Check search domain is not autoFilled if it matches a visited domain");
   yield check_autocomplete({
     search: "example",
new file mode 100644
--- /dev/null
+++ b/toolkit/components/places/tests/unifiedcomplete/test_searchSuggestions.js
@@ -0,0 +1,147 @@
+Cu.import("resource://gre/modules/FormHistory.jsm");
+
+const ENGINE_NAME = "engine-suggestions.xml";
+const SERVER_PORT = 9000;
+const SUGGEST_PREF = "browser.urlbar.suggest.searches";
+
+// Set this to some other function to change how the server converts search
+// strings into suggestions.
+let suggestionsFromSearchString = searchStr => {
+  let suffixes = ["foo", "bar"];
+  return suffixes.map(s => searchStr + " " + s);
+};
+
+add_task(function* setUp() {
+  // Set up a server that provides some suggestions by appending strings onto
+  // the search query.
+  let server = makeTestServer(SERVER_PORT);
+  server.registerPathHandler("/suggest", (req, resp) => {
+    // URL query params are x-www-form-urlencoded, which converts spaces into
+    // plus signs, so un-convert any plus signs back to spaces.
+    let searchStr = decodeURIComponent(req.queryString.replace(/\+/g, " "));
+    let suggestions = suggestionsFromSearchString(searchStr);
+    let data = [searchStr, suggestions];
+    resp.setHeader("Content-Type", "application/json", false);
+    resp.write(JSON.stringify(data));
+  });
+
+  // Install the test engine.
+  let oldCurrentEngine = Services.search.currentEngine;
+  do_register_cleanup(() => Services.search.currentEngine = oldCurrentEngine);
+  let engine = yield addTestEngine(ENGINE_NAME, server);
+  Services.search.currentEngine = engine;
+
+  yield cleanup();
+});
+
+add_task(function* disabled() {
+  Services.prefs.setBoolPref(SUGGEST_PREF, false);
+  yield check_autocomplete({
+    search: "hello",
+    matches: [],
+  });
+  yield cleanup();
+});
+
+add_task(function* singleWordQuery() {
+  Services.prefs.setBoolPref(SUGGEST_PREF, true);
+
+  let searchStr = "hello";
+  yield check_autocomplete({
+    search: searchStr,
+    matches: [{
+      uri: makeActionURI(("searchengine"), {
+        engineName: ENGINE_NAME,
+        input: searchStr,
+        searchQuery: searchStr,
+        searchSuggestion: "hello foo",
+      }),
+      title: ENGINE_NAME,
+      style: ["action", "searchengine"],
+      icon: "",
+    }, {
+      uri: makeActionURI(("searchengine"), {
+        engineName: ENGINE_NAME,
+        input: searchStr,
+        searchQuery: searchStr,
+        searchSuggestion: "hello bar",
+      }),
+      title: ENGINE_NAME,
+      style: ["action", "searchengine"],
+      icon: "",
+    }],
+  });
+
+  yield cleanup();
+});
+
+add_task(function* multiWordQuery() {
+  Services.prefs.setBoolPref(SUGGEST_PREF, true);
+
+  let searchStr = "hello world";
+  yield check_autocomplete({
+    search: searchStr,
+    matches: [{
+      uri: makeActionURI(("searchengine"), {
+        engineName: ENGINE_NAME,
+        input: searchStr,
+        searchQuery: searchStr,
+        searchSuggestion: "hello world foo",
+      }),
+      title: ENGINE_NAME,
+      style: ["action", "searchengine"],
+      icon: "",
+    }, {
+      uri: makeActionURI(("searchengine"), {
+        engineName: ENGINE_NAME,
+        input: searchStr,
+        searchQuery: searchStr,
+        searchSuggestion: "hello world bar",
+      }),
+      title: ENGINE_NAME,
+      style: ["action", "searchengine"],
+      icon: "",
+    }],
+  });
+
+  yield cleanup();
+});
+
+add_task(function* suffixMatch() {
+  Services.prefs.setBoolPref(SUGGEST_PREF, true);
+
+  let oldFn = suggestionsFromSearchString;
+  suggestionsFromSearchString = searchStr => {
+    let prefixes = ["baz", "quux"];
+    return prefixes.map(p => p + " " + searchStr);
+  };
+
+  let searchStr = "hello";
+  yield check_autocomplete({
+    search: searchStr,
+    matches: [{
+      uri: makeActionURI(("searchengine"), {
+        engineName: ENGINE_NAME,
+        input: searchStr,
+        searchQuery: searchStr,
+        searchSuggestion: "baz hello",
+      }),
+      title: ENGINE_NAME,
+      style: ["action", "searchengine"],
+      icon: "",
+    }, {
+      uri: makeActionURI(("searchengine"), {
+        engineName: ENGINE_NAME,
+        input: searchStr,
+        searchQuery: searchStr,
+        searchSuggestion: "quux hello",
+      }),
+      title: ENGINE_NAME,
+      style: ["action", "searchengine"],
+      icon: "",
+    }],
+  });
+
+  suggestionsFromSearchString = oldFn;
+  yield cleanup();
+});
--- a/toolkit/components/places/tests/unifiedcomplete/xpcshell.ini
+++ b/toolkit/components/places/tests/unifiedcomplete/xpcshell.ini
@@ -1,15 +1,15 @@
 [DEFAULT]
 head = head_autocomplete.js
 tail = 
 skip-if = toolkit == 'android' || toolkit == 'gonk'
 support-files =
   data/engine-rel-searchform.xml
-
+  data/engine-suggestions.xml
 
 [test_416211.js]
 [test_416214.js]
 [test_417798.js]
 [test_418257.js]
 [test_422277.js]
 [test_autocomplete_functional.js]
 [test_autocomplete_on_value_removed_479089.js]
@@ -29,16 +29,17 @@ support-files =
 [test_keywords.js]
 [test_match_beginning.js]
 [test_multi_word_search.js]
 [test_queryurl.js]
 [test_searchEngine_alias.js]
 [test_searchEngine_current.js]
 [test_searchEngine_host.js]
 [test_searchEngine_restyle.js]
+[test_searchSuggestions.js]
 [test_special_search.js]
 [test_swap_protocol.js]
 [test_tabmatches.js]
 [test_trimming.js]
 [test_typed.js]
 [test_visiturl.js]
 [test_word_boundary_search.js]
 [test_zero_frecency.js]