More cleanup for Bug 422869 - Clean up autocomplete tests for use as a template for future tests
authoredward.lee@engineering.uiuc.edu
Thu, 27 Mar 2008 11:18:20 -0700
changeset 13635 2e5a62737c4218f739b25280e0f5916682615313
parent 13634 d33e1bfd3e3d948fef157cabff40b8ce7202b011
child 13636 ffe602dd2dd9f9bb7188a44e0ec6a9bb72d18e51
push idunknown
push userunknown
push dateunknown
bugs422869
milestone1.9pre
More cleanup for Bug 422869 - Clean up autocomplete tests for use as a template for future tests
toolkit/components/places/tests/unit/test_000_frecency.js
toolkit/components/places/tests/unit/test_416211.js
toolkit/components/places/tests/unit/test_416214.js
toolkit/components/places/tests/unit/test_417798.js
toolkit/components/places/tests/unit/test_multi_word_search.js
toolkit/components/places/tests/unit/test_word_boundary_search.js
--- a/toolkit/components/places/tests/unit/test_000_frecency.js
+++ b/toolkit/components/places/tests/unit/test_000_frecency.js
@@ -1,9 +1,8 @@
-version(180);
 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 sts=2 et: */
 /* ***** BEGIN LICENSE BLOCK *****
  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  *
  * The contents of this file are subject to the Mozilla Public License Version
  * 1.1 (the "License"); you may not use this file except in compliance with
  * the License. You may obtain a copy of the License at
--- a/toolkit/components/places/tests/unit/test_416211.js
+++ b/toolkit/components/places/tests/unit/test_416211.js
@@ -1,166 +1,231 @@
-version(180);
-/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim:set ts=2 sw=2 sts=2 et: */
 /* ***** BEGIN LICENSE BLOCK *****
  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  *
  * The contents of this file are subject to the Mozilla Public License Version
  * 1.1 (the "License"); you may not use this file except in compliance with
  * the License. You may obtain a copy of the License at
  * http://www.mozilla.org/MPL/
  *
  * Software distributed under the License is distributed on an "AS IS" basis,
  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  * for the specific language governing rights and limitations under the
  * License.
  *
- * The Original Code is Bug 378079 unit test code.
+ * The Original Code is Places Test Code.
  *
- * The Initial Developer of the Original Code is POTI Inc.
- * Portions created by the Initial Developer are Copyright (C) 2007
+ * The Initial Developer of the Original Code is
+ * Edward Lee <edward.lee@engineering.uiuc.edu>.
+ * Portions created by the Initial Developer are Copyright (C) 2008
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
- *   Erwan Loisant <eloisant@gmail.com>
- *   Edward Lee <edward.lee@engineering.uiuc.edu>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 /*
+ * Test bug 416211 to make sure results that match the tag show the bookmark
+ * title instead of the page title.
+ */
 
-Test showing bookmark title when matching the tag for bug 416211
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+let current_test = 0;
+
+function AutoCompleteInput(aSearches) {
+  this.searches = aSearches;
+}
+AutoCompleteInput.prototype = {
+  timeout: 10,
+  textValue: "",
+  searches: null,
+  searchParam: "",
+  popupOpen: false,
+  minResultsForPopup: 0,
+  invalidate: function() {},
+  disableAutoComplete: false,
+  completeDefaultIndex: false,
+  get popup() { return this; },
+  onSearchBegin: function() {},
+  onSearchComplete: function() {},
+  setSelectedIndex: function() {},
+  get searchCount() { return this.searches.length; },
+  getSearchAt: function(aIndex) this.searches[aIndex],
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIAutoCompleteInput, Ci.nsIAutoCompletePopup])
+};
+
+function ensure_results(aSearch, aExpected)
+{
+  let controller = Cc["@mozilla.org/autocomplete/controller;1"].
+                   getService(Ci.nsIAutoCompleteController);
+
+  // Make an AutoCompleteInput that uses our searches
+  // and confirms results on search complete
+  let input = new AutoCompleteInput(["history"]);
+
+  controller.input = input;
+
+  let numSearchesStarted = 0;
+  input.onSearchBegin = function() {
+    numSearchesStarted++;
+    do_check_eq(numSearchesStarted, 1);
+  };
+
+  input.onSearchComplete = function() {
+    do_check_eq(numSearchesStarted, 1);
+    aExpected = aExpected.slice();
 
-- add a visit for a page
-- add a tag for the page
-- add a bookmark with a title
-- search for the tag
-- test number of matches (should be exactly one)
-- make sure the bookmark title is returned
+    // Check to see the expected uris and titles match up (in any order)
+    for (let i = 0; i < controller.matchCount; i++) {
+      let value = controller.getValueAt(i);
+      let comment = controller.getCommentAt(i);
+
+      print("Looking for an expected result of " + value + ", " + comment + "...");
+      let j;
+      for (j = 0; j < aExpected.length; j++) {
+        let [uri, title] = aExpected[j];
+
+        // Skip processed expected results
+        if (uri == undefined) continue;
+
+        // Load the real uri and titles
+        [uri, title] = [iosvc.newURI(kURIs[uri], null, null).spec, kTitles[title]];
+
+        // Got a match on both uri and title?
+        if (uri == value && title == comment) {
+          print("Got it at index " + j + "!!");
+          // Make it undefined so we don't process it again
+          aExpected[j] = [,];
+          break;
+        }
+      }
 
-*/
+      // We didn't hit the break, so we must have not found it
+      if (j == aExpected.length)
+        do_throw("Didn't find the current result (" + value + ", " + comment + ") in expected: " + aExpected);
+    }
+
+    // Make sure we have the right number of results
+    do_check_eq(controller.matchCount, aExpected.length);
+
+    // If we expect results, make sure we got matches
+    do_check_eq(controller.searchStatus, aExpected.length ?
+                Ci.nsIAutoCompleteController.STATUS_COMPLETE_MATCH :
+                Ci.nsIAutoCompleteController.STATUS_COMPLETE_NO_MATCH);
 
+    // Fetch the next test if we have more
+    if (++current_test < gTests.length)
+      run_test();
+
+    do_test_finished();
+  };
+
+  print("Searching for.. " + aSearch);
+  controller.startSearch(aSearch);
+}
+
+// Get history services
 try {
   var histsvc = Cc["@mozilla.org/browser/nav-history-service;1"].
                 getService(Ci.nsINavHistoryService);
   var bhist = histsvc.QueryInterface(Ci.nsIBrowserHistory);
   var bmsvc = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
               getService(Ci.nsINavBookmarksService);
-  var tagssvc = Cc["@mozilla.org/browser/tagging-service;1"].
-                getService(Ci.nsITaggingService);
-} catch (ex) {
+  var tagsvc = Cc["@mozilla.org/browser/tagging-service;1"].
+               getService(Ci.nsITaggingService);
+  var iosvc = Cc["@mozilla.org/network/io-service;1"].
+              getService(Ci.nsIIOService);
+} catch(ex) {
   do_throw("Could not get services\n");
 }
 
-function add_visit(aURI, aVisitDate, aVisitType) {
-  var isRedirect = aVisitType == histsvc.TRANSITION_REDIRECT_PERMANENT ||
-                   aVisitType == histsvc.TRANSITION_REDIRECT_TEMPORARY;
-  var placeID = histsvc.addVisit(aURI, aVisitDate, null,
-                                 aVisitType, isRedirect, 0);
-  do_check_true(placeID > 0);
-  return placeID;
-}
-
-// create test data
-var theTag = "superTag";
-var url = uri("http://www.foobar.com/");
-var title = "Cool Title";
-bhist.addPageWithDetails(url, theTag, Date.now());
+// Some date not too long ago
+let gDate = new Date(Date.now() - 1000 * 60 * 60) * 1000;
 
-tagssvc.tagURI(url, [theTag]);
-bmsvc.insertBookmark(bmsvc.unfiledBookmarksFolder, url, bmsvc.DEFAULT_INDEX, title);
-
-function AutoCompleteInput(aSearches) {
-  this.searches = aSearches;
-}
+function addPageBook(aURI, aTitle, aBook, aTags, aKey)
+{
+  let uri = iosvc.newURI(kURIs[aURI], null, null);
+  let title = kTitles[aTitle];
 
-AutoCompleteInput.prototype = {
-  constructor: AutoCompleteInput, 
-
-  searches: null,
+  let out = [aURI, aTitle, aBook, aTags, aKey];
+  out.push("\nuri=" + kURIs[aURI]);
+  out.push("\ntitle=" + title);
 
-  minResultsForPopup: 0,
-  timeout: 10,
-  searchParam: "",
-  textValue: "",
-  disableAutoComplete: false,  
-  completeDefaultIndex: false,
+  // Add the page and a visit for good measure
+  bhist.addPageWithDetails(uri, title, gDate);
 
-  get searchCount() {
-    return this.searches.length;
-  },
-
-  getSearchAt: function(aIndex) {
-    return this.searches[aIndex];
-  },
+  // Add a bookmark if we need to
+  if (aBook != undefined) {
+    let book = kTitles[aBook];
+    let bmid = bmsvc.insertBookmark(bmsvc.unfiledBookmarksFolder, uri,
+      bmsvc.DEFAULT_INDEX, book);
+    out.push("\nbook=" + book);
 
-  onSearchComplete: function() {},
-
-  popupOpen: false,
-
-  popup: {
-    setSelectedIndex: function(aIndex) {},
-    invalidate: function() {},
+    // Add a keyword to the bookmark if we need to
+    if (aKey != undefined)
+      bmsvc.setKeywordForBookmark(bmid, aKey);
 
-    // nsISupports implementation
-    QueryInterface: function(iid) {
-      if (iid.equals(Ci.nsISupports) ||
-          iid.equals(Ci.nsIAutoCompletePopup))
-        return this;
-
-      throw Components.results.NS_ERROR_NO_INTERFACE;
+    // Add tags if we need to
+    if (aTags != undefined && aTags.length > 0) {
+      // Convert each tag index into the title
+      let tags = aTags.map(function(aTag) kTitles[aTag]);
+      tagsvc.tagURI(uri, tags);
+      out.push("\ntags=" + tags);
     }
-  },
+  }
 
-  // nsISupports implementation
-  QueryInterface: function(iid) {
-    if (iid.equals(Ci.nsISupports) ||
-        iid.equals(Ci.nsIAutoCompleteInput))
-      return this;
-
-    throw Components.results.NS_ERROR_NO_INTERFACE;
-  }
+  print("\nAdding page/book/tag: " + out.join(", "));
 }
 
 function run_test() {
-  var controller = Components.classes["@mozilla.org/autocomplete/controller;1"].
-                   getService(Components.interfaces.nsIAutoCompleteController);
-
-  // Make an AutoCompleteInput that uses our searches
-  // and confirms results on search complete
-  var input = new AutoCompleteInput(["history"]);
-
-  controller.input = input;
-
+  print("\n");
   // Search is asynchronous, so don't let the test finish immediately
   do_test_pending();
 
-  input.onSearchComplete = function() {
-    do_check_eq(controller.searchStatus,
-                Ci.nsIAutoCompleteController.STATUS_COMPLETE_MATCH);
+  // Load the test and print a description then run the test
+  let [description, search, expected, func] = gTests[current_test];
+  print(description);
+
+  // Do an extra function if necessary
+  if (func)
+    func();
 
-    // test that we found the entry we added
-    do_check_eq(controller.matchCount, 1);
+  ensure_results(search, expected);
+}
+
+// *************************************************
+// *** vvv Custom Test Stuff Goes Below Here vvv ***
+// *************************************************
+
+let theTag = "superTag";
 
-    // Make sure the bookmark title is returned
-    do_check_eq(controller.getCommentAt(0), title);
-    /* XXX bug 418257 to look at RTL issues of appending tags
-    do_check_eq(controller.getCommentAt(0), title + " (" + theTag + ")");
-    */
+// Define some shared uris and titles (each page needs its own uri)
+let kURIs = [
+  "http://theuri/",
+];
+let kTitles = [
+  "Page title",
+  "Bookmark title",
+  theTag,
+];
 
-    do_test_finished();
-  };
+// Add page with a title, bookmark, and tag
+addPageBook(0, 0, 1, [2]);
 
-  controller.startSearch(theTag);
-}
+// For each test, provide a title, the search terms, and an array of
+// [uri,title] indices of the pages that should be returned, followed by an
+// optional function
+let gTests = [
+  ["0: Make sure the tag match gives the bookmark title",
+   theTag, [[0,1]]],
+];
--- a/toolkit/components/places/tests/unit/test_416214.js
+++ b/toolkit/components/places/tests/unit/test_416214.js
@@ -1,169 +1,240 @@
-/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim:set ts=2 sw=2 sts=2 et: */
 /* ***** BEGIN LICENSE BLOCK *****
  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  *
  * The contents of this file are subject to the Mozilla Public License Version
  * 1.1 (the "License"); you may not use this file except in compliance with
  * the License. You may obtain a copy of the License at
  * http://www.mozilla.org/MPL/
  *
  * Software distributed under the License is distributed on an "AS IS" basis,
  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  * for the specific language governing rights and limitations under the
  * License.
  *
- * The Original Code is Bug 378079 unit test code.
+ * The Original Code is Places Test Code.
  *
- * The Initial Developer of the Original Code is POTI Inc.
- * Portions created by the Initial Developer are Copyright (C) 2007
+ * The Initial Developer of the Original Code is
+ * Edward Lee <edward.lee@engineering.uiuc.edu>.
+ * Portions created by the Initial Developer are Copyright (C) 2008
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Erwan Loisant <eloisant@gmail.com>
- *   Edward Lee <edward.lee@engineering.uiuc.edu>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 /*
-
-Test autocomplete for non-English URLs that match the tag bug 416214
-Also test bug 417441 by making sure escaped ascii characters like "+" remain
-escaped.
-
-- add a visit for a page with a non-English URL
-- add a tag for the page
-- search for the tag
-- test number of matches (should be exactly one)
-- make sure the url is decoded
-
-*/
+ * Test autocomplete for non-English URLs that match the tag bug 416214. Also
+ * test bug 417441 by making sure escaped ascii characters like "+" remain
+ * escaped.
+ *
+ * - add a visit for a page with a non-English URL
+ * - add a tag for the page
+ * - search for the tag
+ * - test number of matches (should be exactly one)
+ * - make sure the url is decoded
+ */
 
-try {
-  var histsvc = Cc["@mozilla.org/browser/nav-history-service;1"].
-                getService(Ci.nsINavHistoryService);
-  var bmsvc = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
-              getService(Ci.nsINavBookmarksService);
-  var tagssvc = Cc["@mozilla.org/browser/tagging-service;1"].
-                getService(Ci.nsITaggingService);
-} catch (ex) {
-  do_throw("Could not get services\n");
-}
-
-function add_visit(aURI, aVisitDate, aVisitType) {
-  var isRedirect = aVisitType == histsvc.TRANSITION_REDIRECT_PERMANENT ||
-                   aVisitType == histsvc.TRANSITION_REDIRECT_TEMPORARY;
-  var placeID = histsvc.addVisit(aURI, aVisitDate, null,
-                                 aVisitType, isRedirect, 0);
-  do_check_true(placeID > 0);
-  return placeID;
-}
-
-// create test data
-var searchTerm = "ユニコード";
-var theTag = "superTag";
-var decoded = "http://www.foobar.com/" + searchTerm + "/blocking-firefox3%2B";
-var url = uri(decoded);
-add_visit(url, Date.now(), Ci.nsINavHistoryService.TRANSITION_LINK);
-tagssvc.tagURI(url, [theTag]);
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+let current_test = 0;
 
 function AutoCompleteInput(aSearches) {
   this.searches = aSearches;
 }
-
 AutoCompleteInput.prototype = {
-  constructor: AutoCompleteInput, 
-
-  searches: null,
-
-  minResultsForPopup: 0,
   timeout: 10,
-  searchParam: "",
   textValue: "",
-  disableAutoComplete: false,  
+  searches: null,
+  searchParam: "",
+  popupOpen: false,
+  minResultsForPopup: 0,
+  invalidate: function() {},
+  disableAutoComplete: false,
   completeDefaultIndex: false,
-
-  get searchCount() {
-    return this.searches.length;
-  },
-
-  getSearchAt: function(aIndex) {
-    return this.searches[aIndex];
-  },
-
+  get popup() { return this; },
   onSearchBegin: function() {},
   onSearchComplete: function() {},
-
-  popupOpen: false,
-
-  popup: {
-    setSelectedIndex: function(aIndex) {},
-    invalidate: function() {},
-
-    // nsISupports implementation
-    QueryInterface: function(iid) {
-      if (iid.equals(Ci.nsISupports) ||
-          iid.equals(Ci.nsIAutoCompletePopup))
-        return this;
+  setSelectedIndex: function() {},
+  get searchCount() { return this.searches.length; },
+  getSearchAt: function(aIndex) this.searches[aIndex],
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIAutoCompleteInput, Ci.nsIAutoCompletePopup])
+};
 
-      throw Components.results.NS_ERROR_NO_INTERFACE;
-    }
-  },
-
-  // nsISupports implementation
-  QueryInterface: function(iid) {
-    if (iid.equals(Ci.nsISupports) ||
-        iid.equals(Ci.nsIAutoCompleteInput))
-      return this;
-
-    throw Components.results.NS_ERROR_NO_INTERFACE;
-  }
-}
-
-function run_test() {
-  var controller = Components.classes["@mozilla.org/autocomplete/controller;1"].
-                   getService(Components.interfaces.nsIAutoCompleteController);
+function ensure_results(aSearch, aExpected)
+{
+  let controller = Cc["@mozilla.org/autocomplete/controller;1"].
+                   getService(Ci.nsIAutoCompleteController);
 
   // Make an AutoCompleteInput that uses our searches
   // and confirms results on search complete
-  var input = new AutoCompleteInput(["history"]);
+  let input = new AutoCompleteInput(["history"]);
 
   controller.input = input;
 
-  // Search is asynchronous, so don't let the test finish immediately
-  do_test_pending();
-
-  var numSearchesStarted = 0;
+  let numSearchesStarted = 0;
   input.onSearchBegin = function() {
     numSearchesStarted++;
     do_check_eq(numSearchesStarted, 1);
   };
 
   input.onSearchComplete = function() {
     do_check_eq(numSearchesStarted, 1);
-    do_check_eq(controller.searchStatus,
-                Ci.nsIAutoCompleteController.STATUS_COMPLETE_MATCH);
+    aExpected = aExpected.slice();
+
+    // Check to see the expected uris and titles match up (in any order)
+    for (let i = 0; i < controller.matchCount; i++) {
+      let value = controller.getValueAt(i);
+      let comment = controller.getCommentAt(i);
+
+      print("Looking for an expected result of " + value + ", " + comment + "...");
+      let j;
+      for (j = 0; j < aExpected.length; j++) {
+        let [uri, title] = aExpected[j];
+
+        // Skip processed expected results
+        if (uri == undefined) continue;
+
+        // Load the real uri and titles
+        [uri, title] = [iosvc.newURI(kURIs[uri], null, null).spec, kTitles[title]];
 
-    // test that we found the entry we added
-    do_check_eq(controller.matchCount, 1);
+        // Got a match on both uri and title?
+        if (uri == value && title == comment) {
+          print("Got it at index " + j + "!!");
+          // Make it undefined so we don't process it again
+          aExpected[j] = [,];
+          break;
+        }
+      }
 
-    // Make sure the url is the one with the decoded search string
-    do_check_eq(controller.getValueAt(0), url.spec);
+      // We didn't hit the break, so we must have not found it
+      if (j == aExpected.length)
+        do_throw("Didn't find the current result (" + value + ", " + comment + ") in expected: " + aExpected);
+    }
+
+    // Make sure we have the right number of results
+    do_check_eq(controller.matchCount, aExpected.length);
+
+    // If we expect results, make sure we got matches
+    do_check_eq(controller.searchStatus, aExpected.length ?
+                Ci.nsIAutoCompleteController.STATUS_COMPLETE_MATCH :
+                Ci.nsIAutoCompleteController.STATUS_COMPLETE_NO_MATCH);
+
+    // Fetch the next test if we have more
+    if (++current_test < gTests.length)
+      run_test();
 
     do_test_finished();
   };
 
-  controller.startSearch(theTag);
+  print("Searching for.. " + aSearch);
+  controller.startSearch(aSearch);
+}
+
+// Get history services
+try {
+  var histsvc = Cc["@mozilla.org/browser/nav-history-service;1"].
+                getService(Ci.nsINavHistoryService);
+  var bhist = histsvc.QueryInterface(Ci.nsIBrowserHistory);
+  var bmsvc = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
+              getService(Ci.nsINavBookmarksService);
+  var tagsvc = Cc["@mozilla.org/browser/tagging-service;1"].
+               getService(Ci.nsITaggingService);
+  var iosvc = Cc["@mozilla.org/network/io-service;1"].
+              getService(Ci.nsIIOService);
+} catch(ex) {
+  do_throw("Could not get services\n");
 }
+
+// Some date not too long ago
+let gDate = new Date(Date.now() - 1000 * 60 * 60) * 1000;
+
+function addPageBook(aURI, aTitle, aBook, aTags, aKey)
+{
+  let uri = iosvc.newURI(kURIs[aURI], null, null);
+  let title = kTitles[aTitle];
+
+  let out = [aURI, aTitle, aBook, aTags, aKey];
+  out.push("\nuri=" + kURIs[aURI]);
+  out.push("\ntitle=" + title);
+
+  // Add the page and a visit for good measure
+  bhist.addPageWithDetails(uri, title, gDate);
+
+  // Add a bookmark if we need to
+  if (aBook != undefined) {
+    let book = kTitles[aBook];
+    let bmid = bmsvc.insertBookmark(bmsvc.unfiledBookmarksFolder, uri,
+      bmsvc.DEFAULT_INDEX, book);
+    out.push("\nbook=" + book);
+
+    // Add a keyword to the bookmark if we need to
+    if (aKey != undefined)
+      bmsvc.setKeywordForBookmark(bmid, aKey);
+
+    // Add tags if we need to
+    if (aTags != undefined && aTags.length > 0) {
+      // Convert each tag index into the title
+      let tags = aTags.map(function(aTag) kTitles[aTag]);
+      tagsvc.tagURI(uri, tags);
+      out.push("\ntags=" + tags);
+    }
+  }
+
+  print("\nAdding page/book/tag: " + out.join(", "));
+}
+
+function run_test() {
+  print("\n");
+  // Search is asynchronous, so don't let the test finish immediately
+  do_test_pending();
+
+  // Load the test and print a description then run the test
+  let [description, search, expected, func] = gTests[current_test];
+  print(description);
+
+  // Do an extra function if necessary
+  if (func)
+    func();
+
+  ensure_results(search, expected);
+}
+
+// *************************************************
+// *** vvv Custom Test Stuff Goes Below Here vvv ***
+// *************************************************
+
+let theTag = "superTag";
+
+// Define some shared uris and titles (each page needs its own uri)
+let kURIs = [
+  "http://escaped/ユニコード",
+  "http://asciiescaped/blocking-firefox3%2B",
+];
+let kTitles = [
+  "title",
+  theTag,
+];
+
+// Add pages that match the tag
+addPageBook(0, 0, 0, [1]);
+addPageBook(1, 0, 0, [1]);
+
+// For each test, provide a title, the search terms, and an array of
+// [uri,title] indices of the pages that should be returned, followed by an
+// optional function
+let gTests = [
+  ["0: Make sure tag matches return the right url as well as '+' remain escaped",
+   theTag, [[0,0],[1,0]]],
+];
--- a/toolkit/components/places/tests/unit/test_417798.js
+++ b/toolkit/components/places/tests/unit/test_417798.js
@@ -1,31 +1,29 @@
-/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim:set ts=2 sw=2 sts=2 et: */
 /* ***** BEGIN LICENSE BLOCK *****
  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  *
  * The contents of this file are subject to the Mozilla Public License Version
  * 1.1 (the "License"); you may not use this file except in compliance with
  * the License. You may obtain a copy of the License at
  * http://www.mozilla.org/MPL/
  *
  * Software distributed under the License is distributed on an "AS IS" basis,
  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  * for the specific language governing rights and limitations under the
  * License.
  *
- * The Original Code is Bug 378079 unit test code.
+ * The Original Code is Places Test Code.
  *
- * The Initial Developer of the Original Code is POTI Inc.
- * Portions created by the Initial Developer are Copyright (C) 2007
+ * The Initial Developer of the Original Code is
+ * Edward Lee <edward.lee@engineering.uiuc.edu>.
+ * Portions created by the Initial Developer are Copyright (C) 2008
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
- *   Edward Lee <edward.lee@engineering.uiuc.edu>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -80,16 +78,17 @@ function ensure_results(aSearch, aExpect
   let numSearchesStarted = 0;
   input.onSearchBegin = function() {
     numSearchesStarted++;
     do_check_eq(numSearchesStarted, 1);
   };
 
   input.onSearchComplete = function() {
     do_check_eq(numSearchesStarted, 1);
+    aExpected = aExpected.slice();
 
     // Check to see the expected uris and titles match up (in any order)
     for (let i = 0; i < controller.matchCount; i++) {
       let value = controller.getValueAt(i);
       let comment = controller.getCommentAt(i);
 
       print("Looking for an expected result of " + value + ", " + comment + "...");
       let j;
--- a/toolkit/components/places/tests/unit/test_multi_word_search.js
+++ b/toolkit/components/places/tests/unit/test_multi_word_search.js
@@ -1,33 +1,29 @@
-/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim:set ts=2 sw=2 sts=2 et: */
 /* ***** BEGIN LICENSE BLOCK *****
  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  *
  * The contents of this file are subject to the Mozilla Public License Version
  * 1.1 (the "License"); you may not use this file except in compliance with
  * the License. You may obtain a copy of the License at
  * http://www.mozilla.org/MPL/
  *
  * Software distributed under the License is distributed on an "AS IS" basis,
  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  * for the specific language governing rights and limitations under the
  * License.
  *
- * The Original Code is Bug 378079 unit test code.
+ * The Original Code is Places Test Code.
  *
- * The Initial Developer of the Original Code is POTI Inc.
- * Portions created by the Initial Developer are Copyright (C) 2007
+ * The Initial Developer of the Original Code is
+ * Edward Lee <edward.lee@engineering.uiuc.edu>.
+ * Portions created by the Initial Developer are Copyright (C) 2008
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
- *   Matt Crocker <matt@songbirdnest.com>
- *   Seth Spitzer <sspitzer@mozilla.org>
- *   Edward Lee <edward.lee@engineering.uiuc.edu>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -87,16 +83,17 @@ function ensure_results(aSearch, aExpect
   let numSearchesStarted = 0;
   input.onSearchBegin = function() {
     numSearchesStarted++;
     do_check_eq(numSearchesStarted, 1);
   };
 
   input.onSearchComplete = function() {
     do_check_eq(numSearchesStarted, 1);
+    aExpected = aExpected.slice();
 
     // Check to see the expected uris and titles match up (in any order)
     for (let i = 0; i < controller.matchCount; i++) {
       let value = controller.getValueAt(i);
       let comment = controller.getCommentAt(i);
 
       print("Looking for an expected result of " + value + ", " + comment + "...");
       let j;
--- a/toolkit/components/places/tests/unit/test_word_boundary_search.js
+++ b/toolkit/components/places/tests/unit/test_word_boundary_search.js
@@ -6,17 +6,17 @@
  * the License. You may obtain a copy of the License at
  * http://www.mozilla.org/MPL/
  *
  * Software distributed under the License is distributed on an "AS IS" basis,
  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  * for the specific language governing rights and limitations under the
  * License.
  *
- * The Original Code is Places Word Boundary Search Test Code.
+ * The Original Code is Places Test Code.
  *
  * The Initial Developer of the Original Code is
  * Edward Lee <edward.lee@engineering.uiuc.edu>.
  * Portions created by the Initial Developer are Copyright (C) 2008
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *
@@ -78,16 +78,17 @@ function ensure_results(aSearch, aExpect
   let numSearchesStarted = 0;
   input.onSearchBegin = function() {
     numSearchesStarted++;
     do_check_eq(numSearchesStarted, 1);
   };
 
   input.onSearchComplete = function() {
     do_check_eq(numSearchesStarted, 1);
+    aExpected = aExpected.slice();
 
     // Check to see the expected uris and titles match up (in any order)
     for (let i = 0; i < controller.matchCount; i++) {
       let value = controller.getValueAt(i);
       let comment = controller.getCommentAt(i);
 
       print("Looking for an expected result of " + value + ", " + comment + "...");
       let j;
@@ -258,18 +259,18 @@ let gTests = [
   ["1: Match 'dont' at the beginning or after /",
    "dont", [[1,0],[3,2],[5,0]]],
   ["2: Match '2' after the slash and after a word (in tags too)",
    "2", [[2,1],[3,2],[4,0],[5,0]]],
   ["3: Match 't' at the beginning or after /",
    "t", [[0,0],[1,0],[2,1],[3,2],[4,0],[5,0],[9,0]]],
   ["4: Match 'word' after many consecutive word boundaries",
    "word", [[6,3]]],
-  ["5: Match a word boundary ':' for everything",
-   ":", [[0,0],[1,0],[2,1],[3,2],[4,0],[5,0],[6,3],[7,4],[8,5],[9,0]]],
+  ["5: Match a word boundary '/' for everything",
+   "/", [[0,0],[1,0],[2,1],[3,2],[4,0],[5,0],[6,3],[7,4],[8,5],[9,0]]],
   ["6: Match word boundaries '()_+' that are among word boundaries",
    "()_+", [[6,3]]],
 
   ["7: Katakana characters form a string, so match the beginning",
    katakana[0], [[7,4]]],
   /*["8: Middle of a katakana word shouldn't be matched",
    katakana[1], []],*/