toolkit/components/places/tests/unit/test_000_frecency.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/toolkit/components/places/tests/unit/test_000_frecency.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,273 @@
     1.4 +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     1.5 +/* vim:set ts=2 sw=2 sts=2 et: */
     1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.9 +
    1.10 +/*
    1.11 +
    1.12 +Autocomplete Frecency Tests
    1.13 +
    1.14 +- add a visit for each score permutation
    1.15 +- search
    1.16 +- test number of matches
    1.17 +- test each item's location in results
    1.18 +
    1.19 +*/
    1.20 +
    1.21 +try {
    1.22 +  var histsvc = Cc["@mozilla.org/browser/nav-history-service;1"].
    1.23 +                getService(Ci.nsINavHistoryService);
    1.24 +  var bmsvc = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
    1.25 +              getService(Ci.nsINavBookmarksService);
    1.26 +  var prefs = Cc["@mozilla.org/preferences-service;1"].
    1.27 +              getService(Ci.nsIPrefBranch);
    1.28 +} catch(ex) {
    1.29 +  do_throw("Could not get services\n");
    1.30 +}
    1.31 +
    1.32 +var bucketPrefs = [
    1.33 +  [ "firstBucketCutoff", "firstBucketWeight"],
    1.34 +  [ "secondBucketCutoff", "secondBucketWeight"],
    1.35 +  [ "thirdBucketCutoff", "thirdBucketWeight"],
    1.36 +  [ "fourthBucketCutoff", "fourthBucketWeight"],
    1.37 +  [ null, "defaultBucketWeight"]
    1.38 +];
    1.39 +
    1.40 +var bonusPrefs = {
    1.41 +  embedVisitBonus: Ci.nsINavHistoryService.TRANSITION_EMBED,
    1.42 +  framedLinkVisitBonus: Ci.nsINavHistoryService.TRANSITION_FRAMED_LINK,
    1.43 +  linkVisitBonus: Ci.nsINavHistoryService.TRANSITION_LINK,
    1.44 +  typedVisitBonus: Ci.nsINavHistoryService.TRANSITION_TYPED,
    1.45 +  bookmarkVisitBonus: Ci.nsINavHistoryService.TRANSITION_BOOKMARK,
    1.46 +  downloadVisitBonus: Ci.nsINavHistoryService.TRANSITION_DOWNLOAD,
    1.47 +  permRedirectVisitBonus: Ci.nsINavHistoryService.TRANSITION_REDIRECT_PERMANENT,
    1.48 +  tempRedirectVisitBonus: Ci.nsINavHistoryService.TRANSITION_REDIRECT_TEMPORARY,
    1.49 +};
    1.50 +
    1.51 +// create test data
    1.52 +var searchTerm = "frecency";
    1.53 +var results = [];
    1.54 +var matchCount = 0;
    1.55 +var now = Date.now();
    1.56 +var prefPrefix = "places.frecency.";
    1.57 +
    1.58 +function task_initializeBucket(bucket) {
    1.59 +  let [cutoffName, weightName] = bucket;
    1.60 +  // get pref values
    1.61 +  var weight = 0, cutoff = 0, bonus = 0;
    1.62 +  try {
    1.63 +    weight = prefs.getIntPref(prefPrefix + weightName);
    1.64 +  } catch(ex) {}
    1.65 +  try {
    1.66 +    cutoff = prefs.getIntPref(prefPrefix + cutoffName);
    1.67 +  } catch(ex) {}
    1.68 +
    1.69 +  if (cutoff < 1)
    1.70 +    return;
    1.71 +
    1.72 +  // generate a date within the cutoff period
    1.73 +  var dateInPeriod = (now - ((cutoff - 1) * 86400 * 1000)) * 1000;
    1.74 +
    1.75 +  for (let [bonusName, visitType] in Iterator(bonusPrefs)) {
    1.76 +    var frecency = -1;
    1.77 +    var calculatedURI = null;
    1.78 +    var matchTitle = "";
    1.79 +    var bonusValue = prefs.getIntPref(prefPrefix + bonusName);
    1.80 +    // unvisited (only for first cutoff date bucket)
    1.81 +    if (bonusName == "unvisitedBookmarkBonus" || bonusName == "unvisitedTypedBonus") {
    1.82 +      if (cutoffName == "firstBucketCutoff") {
    1.83 +        var points = Math.ceil(bonusValue / parseFloat(100.0) * weight);
    1.84 +        var visitCount = 1; //bonusName == "unvisitedBookmarkBonus" ? 1 : 0;
    1.85 +        frecency = Math.ceil(visitCount * points);
    1.86 +        calculatedURI = uri("http://" + searchTerm + ".com/" +
    1.87 +          bonusName + ":" + bonusValue + "/cutoff:" + cutoff +
    1.88 +          "/weight:" + weight + "/frecency:" + frecency);
    1.89 +        if (bonusName == "unvisitedBookmarkBonus") {
    1.90 +          matchTitle = searchTerm + "UnvisitedBookmark";
    1.91 +          bmsvc.insertBookmark(bmsvc.unfiledBookmarksFolder, calculatedURI, bmsvc.DEFAULT_INDEX, matchTitle);
    1.92 +        }
    1.93 +        else {
    1.94 +          matchTitle = searchTerm + "UnvisitedTyped";
    1.95 +          yield promiseAddVisits({
    1.96 +            uri: calculatedURI,
    1.97 +            title: matchTitle,
    1.98 +            transition: visitType,
    1.99 +            visitDate: now
   1.100 +          });
   1.101 +          histsvc.markPageAsTyped(calculatedURI);
   1.102 +        }
   1.103 +      }
   1.104 +    }
   1.105 +    else {
   1.106 +      // visited
   1.107 +      // visited bookmarks get the visited bookmark bonus twice
   1.108 +      if (visitType == Ci.nsINavHistoryService.TRANSITION_BOOKMARK)
   1.109 +        bonusValue = bonusValue * 2;
   1.110 +
   1.111 +      var points = Math.ceil(1 * ((bonusValue / parseFloat(100.000000)).toFixed(6) * weight) / 1);
   1.112 +      if (!points) {
   1.113 +        if (visitType == Ci.nsINavHistoryService.TRANSITION_EMBED ||
   1.114 +            visitType == Ci.nsINavHistoryService.TRANSITION_FRAMED_LINK ||
   1.115 +            visitType == Ci.nsINavHistoryService.TRANSITION_DOWNLOAD ||
   1.116 +            bonusName == "defaultVisitBonus")
   1.117 +          frecency = 0;
   1.118 +        else
   1.119 +          frecency = -1;
   1.120 +      }
   1.121 +      else
   1.122 +        frecency = points;
   1.123 +      calculatedURI = uri("http://" + searchTerm + ".com/" +
   1.124 +        bonusName + ":" + bonusValue + "/cutoff:" + cutoff +
   1.125 +        "/weight:" + weight + "/frecency:" + frecency);
   1.126 +      if (visitType == Ci.nsINavHistoryService.TRANSITION_BOOKMARK) {
   1.127 +        matchTitle = searchTerm + "Bookmarked";
   1.128 +        bmsvc.insertBookmark(bmsvc.unfiledBookmarksFolder, calculatedURI, bmsvc.DEFAULT_INDEX, matchTitle);
   1.129 +      }
   1.130 +      else
   1.131 +        matchTitle = calculatedURI.spec.substr(calculatedURI.spec.lastIndexOf("/")+1);
   1.132 +      yield promiseAddVisits({
   1.133 +        uri: calculatedURI,
   1.134 +        transition: visitType,
   1.135 +        visitDate: dateInPeriod
   1.136 +      });
   1.137 +    }
   1.138 +
   1.139 +    if (calculatedURI && frecency) {
   1.140 +      results.push([calculatedURI, frecency, matchTitle]);
   1.141 +      yield promiseAddVisits({
   1.142 +        uri: calculatedURI,
   1.143 +        title: matchTitle,
   1.144 +        transition: visitType,
   1.145 +        visitDate: dateInPeriod
   1.146 +      });
   1.147 +    }
   1.148 +  }
   1.149 +}
   1.150 +
   1.151 +function AutoCompleteInput(aSearches) {
   1.152 +  this.searches = aSearches;
   1.153 +}
   1.154 +AutoCompleteInput.prototype = {
   1.155 +  constructor: AutoCompleteInput,
   1.156 +
   1.157 +  searches: null,
   1.158 +
   1.159 +  minResultsForPopup: 0,
   1.160 +  timeout: 10,
   1.161 +  searchParam: "",
   1.162 +  textValue: "",
   1.163 +  disableAutoComplete: false,
   1.164 +  completeDefaultIndex: false,
   1.165 +
   1.166 +  get searchCount() {
   1.167 +    return this.searches.length;
   1.168 +  },
   1.169 +
   1.170 +  getSearchAt: function(aIndex) {
   1.171 +    return this.searches[aIndex];
   1.172 +  },
   1.173 +
   1.174 +  onSearchBegin: function() {},
   1.175 +  onSearchComplete: function() {},
   1.176 +
   1.177 +  popupOpen: false,
   1.178 +
   1.179 +  popup: {
   1.180 +    setSelectedIndex: function(aIndex) {},
   1.181 +    invalidate: function() {},
   1.182 +
   1.183 +    // nsISupports implementation
   1.184 +    QueryInterface: function(iid) {
   1.185 +      if (iid.equals(Ci.nsISupports) ||
   1.186 +          iid.equals(Ci.nsIAutoCompletePopup))
   1.187 +        return this;
   1.188 +
   1.189 +      throw Components.results.NS_ERROR_NO_INTERFACE;
   1.190 +    }
   1.191 +  },
   1.192 +
   1.193 +  // nsISupports implementation
   1.194 +  QueryInterface: function(iid) {
   1.195 +    if (iid.equals(Ci.nsISupports) ||
   1.196 +        iid.equals(Ci.nsIAutoCompleteInput))
   1.197 +      return this;
   1.198 +
   1.199 +    throw Components.results.NS_ERROR_NO_INTERFACE;
   1.200 +  }
   1.201 +}
   1.202 +
   1.203 +function run_test()
   1.204 +{
   1.205 +  run_next_test();
   1.206 +}
   1.207 +
   1.208 +add_task(function test_frecency()
   1.209 +{
   1.210 +  for (let [, bucket] in Iterator(bucketPrefs)) {
   1.211 +    yield task_initializeBucket(bucket);
   1.212 +  }
   1.213 +
   1.214 +  // sort results by frecency
   1.215 +  results.sort(function(a,b) b[1] - a[1]);
   1.216 +  // Make sure there's enough results returned
   1.217 +  prefs.setIntPref("browser.urlbar.maxRichResults", results.length);
   1.218 +
   1.219 +  // DEBUG
   1.220 +  //results.every(function(el) { dump("result: " + el[1] + ": " + el[0].spec + " (" + el[2] + ")\n"); return true; })
   1.221 +
   1.222 +  yield promiseAsyncUpdates();
   1.223 +
   1.224 +  var controller = Components.classes["@mozilla.org/autocomplete/controller;1"].
   1.225 +                   getService(Components.interfaces.nsIAutoCompleteController);
   1.226 +
   1.227 +  // Make an AutoCompleteInput that uses our searches
   1.228 +  // and confirms results on search complete
   1.229 +  var input = new AutoCompleteInput(["history"]);
   1.230 +
   1.231 +  controller.input = input;
   1.232 +
   1.233 +  // always search in history + bookmarks, no matter what the default is
   1.234 +  prefs.setIntPref("browser.urlbar.search.sources", 3);
   1.235 +  prefs.setIntPref("browser.urlbar.default.behavior", 0);
   1.236 +
   1.237 +  var numSearchesStarted = 0;
   1.238 +  input.onSearchBegin = function() {
   1.239 +    numSearchesStarted++;
   1.240 +    do_check_eq(numSearchesStarted, 1);
   1.241 +  };
   1.242 +
   1.243 +  let deferred = Promise.defer();
   1.244 +  input.onSearchComplete = function() {
   1.245 +    do_check_eq(numSearchesStarted, 1);
   1.246 +    do_check_eq(controller.searchStatus,
   1.247 +                Ci.nsIAutoCompleteController.STATUS_COMPLETE_MATCH);
   1.248 +
   1.249 +    // test that all records with non-zero frecency were matched
   1.250 +    do_check_eq(controller.matchCount, results.length);
   1.251 +
   1.252 +    // test that matches are sorted by frecency
   1.253 +    for (var i = 0; i < controller.matchCount; i++) {
   1.254 +      let searchURL = controller.getValueAt(i);
   1.255 +      let expectURL = results[i][0].spec;
   1.256 +      if (searchURL == expectURL) {
   1.257 +        do_check_eq(controller.getValueAt(i), results[i][0].spec);
   1.258 +        do_check_eq(controller.getCommentAt(i), results[i][2]);
   1.259 +      } else {
   1.260 +        // If the results didn't match exactly, perhaps it's still the right
   1.261 +        // frecency just in the wrong "order" (order of same frecency is
   1.262 +        // undefined), so check if frecency matches. This is okay because we
   1.263 +        // can still ensure the correct number of expected frecencies.
   1.264 +        let getFrecency = function(aURL) aURL.match(/frecency:(-?\d+)$/)[1];
   1.265 +        print("### checking for same frecency between '" + searchURL +
   1.266 +              "' and '" + expectURL + "'");
   1.267 +        do_check_eq(getFrecency(searchURL), getFrecency(expectURL));
   1.268 +      }
   1.269 +    }
   1.270 +    deferred.resolve();
   1.271 +  };
   1.272 +
   1.273 +  controller.startSearch(searchTerm);
   1.274 +
   1.275 +  yield deferred.promise;
   1.276 +});

mercurial