|
1 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* vim:set ts=2 sw=2 sts=2 et: */ |
|
3 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
4 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
6 |
|
7 /* |
|
8 |
|
9 Autocomplete Frecency Tests |
|
10 |
|
11 - add a visit for each score permutation |
|
12 - search |
|
13 - test number of matches |
|
14 - test each item's location in results |
|
15 |
|
16 */ |
|
17 |
|
18 try { |
|
19 var histsvc = Cc["@mozilla.org/browser/nav-history-service;1"]. |
|
20 getService(Ci.nsINavHistoryService); |
|
21 var bmsvc = Cc["@mozilla.org/browser/nav-bookmarks-service;1"]. |
|
22 getService(Ci.nsINavBookmarksService); |
|
23 var prefs = Cc["@mozilla.org/preferences-service;1"]. |
|
24 getService(Ci.nsIPrefBranch); |
|
25 } catch(ex) { |
|
26 do_throw("Could not get services\n"); |
|
27 } |
|
28 |
|
29 var bucketPrefs = [ |
|
30 [ "firstBucketCutoff", "firstBucketWeight"], |
|
31 [ "secondBucketCutoff", "secondBucketWeight"], |
|
32 [ "thirdBucketCutoff", "thirdBucketWeight"], |
|
33 [ "fourthBucketCutoff", "fourthBucketWeight"], |
|
34 [ null, "defaultBucketWeight"] |
|
35 ]; |
|
36 |
|
37 var bonusPrefs = { |
|
38 embedVisitBonus: Ci.nsINavHistoryService.TRANSITION_EMBED, |
|
39 framedLinkVisitBonus: Ci.nsINavHistoryService.TRANSITION_FRAMED_LINK, |
|
40 linkVisitBonus: Ci.nsINavHistoryService.TRANSITION_LINK, |
|
41 typedVisitBonus: Ci.nsINavHistoryService.TRANSITION_TYPED, |
|
42 bookmarkVisitBonus: Ci.nsINavHistoryService.TRANSITION_BOOKMARK, |
|
43 downloadVisitBonus: Ci.nsINavHistoryService.TRANSITION_DOWNLOAD, |
|
44 permRedirectVisitBonus: Ci.nsINavHistoryService.TRANSITION_REDIRECT_PERMANENT, |
|
45 tempRedirectVisitBonus: Ci.nsINavHistoryService.TRANSITION_REDIRECT_TEMPORARY, |
|
46 }; |
|
47 |
|
48 // create test data |
|
49 var searchTerm = "frecency"; |
|
50 var results = []; |
|
51 var matchCount = 0; |
|
52 var now = Date.now(); |
|
53 var prefPrefix = "places.frecency."; |
|
54 |
|
55 function task_initializeBucket(bucket) { |
|
56 let [cutoffName, weightName] = bucket; |
|
57 // get pref values |
|
58 var weight = 0, cutoff = 0, bonus = 0; |
|
59 try { |
|
60 weight = prefs.getIntPref(prefPrefix + weightName); |
|
61 } catch(ex) {} |
|
62 try { |
|
63 cutoff = prefs.getIntPref(prefPrefix + cutoffName); |
|
64 } catch(ex) {} |
|
65 |
|
66 if (cutoff < 1) |
|
67 return; |
|
68 |
|
69 // generate a date within the cutoff period |
|
70 var dateInPeriod = (now - ((cutoff - 1) * 86400 * 1000)) * 1000; |
|
71 |
|
72 for (let [bonusName, visitType] in Iterator(bonusPrefs)) { |
|
73 var frecency = -1; |
|
74 var calculatedURI = null; |
|
75 var matchTitle = ""; |
|
76 var bonusValue = prefs.getIntPref(prefPrefix + bonusName); |
|
77 // unvisited (only for first cutoff date bucket) |
|
78 if (bonusName == "unvisitedBookmarkBonus" || bonusName == "unvisitedTypedBonus") { |
|
79 if (cutoffName == "firstBucketCutoff") { |
|
80 var points = Math.ceil(bonusValue / parseFloat(100.0) * weight); |
|
81 var visitCount = 1; //bonusName == "unvisitedBookmarkBonus" ? 1 : 0; |
|
82 frecency = Math.ceil(visitCount * points); |
|
83 calculatedURI = uri("http://" + searchTerm + ".com/" + |
|
84 bonusName + ":" + bonusValue + "/cutoff:" + cutoff + |
|
85 "/weight:" + weight + "/frecency:" + frecency); |
|
86 if (bonusName == "unvisitedBookmarkBonus") { |
|
87 matchTitle = searchTerm + "UnvisitedBookmark"; |
|
88 bmsvc.insertBookmark(bmsvc.unfiledBookmarksFolder, calculatedURI, bmsvc.DEFAULT_INDEX, matchTitle); |
|
89 } |
|
90 else { |
|
91 matchTitle = searchTerm + "UnvisitedTyped"; |
|
92 yield promiseAddVisits({ |
|
93 uri: calculatedURI, |
|
94 title: matchTitle, |
|
95 transition: visitType, |
|
96 visitDate: now |
|
97 }); |
|
98 histsvc.markPageAsTyped(calculatedURI); |
|
99 } |
|
100 } |
|
101 } |
|
102 else { |
|
103 // visited |
|
104 // visited bookmarks get the visited bookmark bonus twice |
|
105 if (visitType == Ci.nsINavHistoryService.TRANSITION_BOOKMARK) |
|
106 bonusValue = bonusValue * 2; |
|
107 |
|
108 var points = Math.ceil(1 * ((bonusValue / parseFloat(100.000000)).toFixed(6) * weight) / 1); |
|
109 if (!points) { |
|
110 if (visitType == Ci.nsINavHistoryService.TRANSITION_EMBED || |
|
111 visitType == Ci.nsINavHistoryService.TRANSITION_FRAMED_LINK || |
|
112 visitType == Ci.nsINavHistoryService.TRANSITION_DOWNLOAD || |
|
113 bonusName == "defaultVisitBonus") |
|
114 frecency = 0; |
|
115 else |
|
116 frecency = -1; |
|
117 } |
|
118 else |
|
119 frecency = points; |
|
120 calculatedURI = uri("http://" + searchTerm + ".com/" + |
|
121 bonusName + ":" + bonusValue + "/cutoff:" + cutoff + |
|
122 "/weight:" + weight + "/frecency:" + frecency); |
|
123 if (visitType == Ci.nsINavHistoryService.TRANSITION_BOOKMARK) { |
|
124 matchTitle = searchTerm + "Bookmarked"; |
|
125 bmsvc.insertBookmark(bmsvc.unfiledBookmarksFolder, calculatedURI, bmsvc.DEFAULT_INDEX, matchTitle); |
|
126 } |
|
127 else |
|
128 matchTitle = calculatedURI.spec.substr(calculatedURI.spec.lastIndexOf("/")+1); |
|
129 yield promiseAddVisits({ |
|
130 uri: calculatedURI, |
|
131 transition: visitType, |
|
132 visitDate: dateInPeriod |
|
133 }); |
|
134 } |
|
135 |
|
136 if (calculatedURI && frecency) { |
|
137 results.push([calculatedURI, frecency, matchTitle]); |
|
138 yield promiseAddVisits({ |
|
139 uri: calculatedURI, |
|
140 title: matchTitle, |
|
141 transition: visitType, |
|
142 visitDate: dateInPeriod |
|
143 }); |
|
144 } |
|
145 } |
|
146 } |
|
147 |
|
148 function AutoCompleteInput(aSearches) { |
|
149 this.searches = aSearches; |
|
150 } |
|
151 AutoCompleteInput.prototype = { |
|
152 constructor: AutoCompleteInput, |
|
153 |
|
154 searches: null, |
|
155 |
|
156 minResultsForPopup: 0, |
|
157 timeout: 10, |
|
158 searchParam: "", |
|
159 textValue: "", |
|
160 disableAutoComplete: false, |
|
161 completeDefaultIndex: false, |
|
162 |
|
163 get searchCount() { |
|
164 return this.searches.length; |
|
165 }, |
|
166 |
|
167 getSearchAt: function(aIndex) { |
|
168 return this.searches[aIndex]; |
|
169 }, |
|
170 |
|
171 onSearchBegin: function() {}, |
|
172 onSearchComplete: function() {}, |
|
173 |
|
174 popupOpen: false, |
|
175 |
|
176 popup: { |
|
177 setSelectedIndex: function(aIndex) {}, |
|
178 invalidate: function() {}, |
|
179 |
|
180 // nsISupports implementation |
|
181 QueryInterface: function(iid) { |
|
182 if (iid.equals(Ci.nsISupports) || |
|
183 iid.equals(Ci.nsIAutoCompletePopup)) |
|
184 return this; |
|
185 |
|
186 throw Components.results.NS_ERROR_NO_INTERFACE; |
|
187 } |
|
188 }, |
|
189 |
|
190 // nsISupports implementation |
|
191 QueryInterface: function(iid) { |
|
192 if (iid.equals(Ci.nsISupports) || |
|
193 iid.equals(Ci.nsIAutoCompleteInput)) |
|
194 return this; |
|
195 |
|
196 throw Components.results.NS_ERROR_NO_INTERFACE; |
|
197 } |
|
198 } |
|
199 |
|
200 function run_test() |
|
201 { |
|
202 run_next_test(); |
|
203 } |
|
204 |
|
205 add_task(function test_frecency() |
|
206 { |
|
207 for (let [, bucket] in Iterator(bucketPrefs)) { |
|
208 yield task_initializeBucket(bucket); |
|
209 } |
|
210 |
|
211 // sort results by frecency |
|
212 results.sort(function(a,b) b[1] - a[1]); |
|
213 // Make sure there's enough results returned |
|
214 prefs.setIntPref("browser.urlbar.maxRichResults", results.length); |
|
215 |
|
216 // DEBUG |
|
217 //results.every(function(el) { dump("result: " + el[1] + ": " + el[0].spec + " (" + el[2] + ")\n"); return true; }) |
|
218 |
|
219 yield promiseAsyncUpdates(); |
|
220 |
|
221 var controller = Components.classes["@mozilla.org/autocomplete/controller;1"]. |
|
222 getService(Components.interfaces.nsIAutoCompleteController); |
|
223 |
|
224 // Make an AutoCompleteInput that uses our searches |
|
225 // and confirms results on search complete |
|
226 var input = new AutoCompleteInput(["history"]); |
|
227 |
|
228 controller.input = input; |
|
229 |
|
230 // always search in history + bookmarks, no matter what the default is |
|
231 prefs.setIntPref("browser.urlbar.search.sources", 3); |
|
232 prefs.setIntPref("browser.urlbar.default.behavior", 0); |
|
233 |
|
234 var numSearchesStarted = 0; |
|
235 input.onSearchBegin = function() { |
|
236 numSearchesStarted++; |
|
237 do_check_eq(numSearchesStarted, 1); |
|
238 }; |
|
239 |
|
240 let deferred = Promise.defer(); |
|
241 input.onSearchComplete = function() { |
|
242 do_check_eq(numSearchesStarted, 1); |
|
243 do_check_eq(controller.searchStatus, |
|
244 Ci.nsIAutoCompleteController.STATUS_COMPLETE_MATCH); |
|
245 |
|
246 // test that all records with non-zero frecency were matched |
|
247 do_check_eq(controller.matchCount, results.length); |
|
248 |
|
249 // test that matches are sorted by frecency |
|
250 for (var i = 0; i < controller.matchCount; i++) { |
|
251 let searchURL = controller.getValueAt(i); |
|
252 let expectURL = results[i][0].spec; |
|
253 if (searchURL == expectURL) { |
|
254 do_check_eq(controller.getValueAt(i), results[i][0].spec); |
|
255 do_check_eq(controller.getCommentAt(i), results[i][2]); |
|
256 } else { |
|
257 // If the results didn't match exactly, perhaps it's still the right |
|
258 // frecency just in the wrong "order" (order of same frecency is |
|
259 // undefined), so check if frecency matches. This is okay because we |
|
260 // can still ensure the correct number of expected frecencies. |
|
261 let getFrecency = function(aURL) aURL.match(/frecency:(-?\d+)$/)[1]; |
|
262 print("### checking for same frecency between '" + searchURL + |
|
263 "' and '" + expectURL + "'"); |
|
264 do_check_eq(getFrecency(searchURL), getFrecency(expectURL)); |
|
265 } |
|
266 } |
|
267 deferred.resolve(); |
|
268 }; |
|
269 |
|
270 controller.startSearch(searchTerm); |
|
271 |
|
272 yield deferred.promise; |
|
273 }); |