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 279935 31fbd3b1027c89a958d90c448f3d328ccb983864
parent 279934 1a21a5d0e9da9e8508256e97fea93a2efd83c96a
child 279936 d2eb65fdebb3a9808bcfd193c7844727b7341404
push id4932
push userjlund@mozilla.com
push dateMon, 10 Aug 2015 18:23:06 +0000
treeherdermozilla-beta@6dd5a4f5f745 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmak
bugs1173751
milestone41.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 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]