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 +});