michael@0: /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim:set ts=2 sw=2 sts=2 et: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* michael@0: michael@0: Autocomplete Frecency Tests michael@0: michael@0: - add a visit for each score permutation michael@0: - search michael@0: - test number of matches michael@0: - test each item's location in results michael@0: michael@0: */ michael@0: michael@0: try { michael@0: var histsvc = Cc["@mozilla.org/browser/nav-history-service;1"]. michael@0: getService(Ci.nsINavHistoryService); michael@0: var bmsvc = Cc["@mozilla.org/browser/nav-bookmarks-service;1"]. michael@0: getService(Ci.nsINavBookmarksService); michael@0: var prefs = Cc["@mozilla.org/preferences-service;1"]. michael@0: getService(Ci.nsIPrefBranch); michael@0: } catch(ex) { michael@0: do_throw("Could not get services\n"); michael@0: } michael@0: michael@0: var bucketPrefs = [ michael@0: [ "firstBucketCutoff", "firstBucketWeight"], michael@0: [ "secondBucketCutoff", "secondBucketWeight"], michael@0: [ "thirdBucketCutoff", "thirdBucketWeight"], michael@0: [ "fourthBucketCutoff", "fourthBucketWeight"], michael@0: [ null, "defaultBucketWeight"] michael@0: ]; michael@0: michael@0: var bonusPrefs = { michael@0: embedVisitBonus: Ci.nsINavHistoryService.TRANSITION_EMBED, michael@0: framedLinkVisitBonus: Ci.nsINavHistoryService.TRANSITION_FRAMED_LINK, michael@0: linkVisitBonus: Ci.nsINavHistoryService.TRANSITION_LINK, michael@0: typedVisitBonus: Ci.nsINavHistoryService.TRANSITION_TYPED, michael@0: bookmarkVisitBonus: Ci.nsINavHistoryService.TRANSITION_BOOKMARK, michael@0: downloadVisitBonus: Ci.nsINavHistoryService.TRANSITION_DOWNLOAD, michael@0: permRedirectVisitBonus: Ci.nsINavHistoryService.TRANSITION_REDIRECT_PERMANENT, michael@0: tempRedirectVisitBonus: Ci.nsINavHistoryService.TRANSITION_REDIRECT_TEMPORARY, michael@0: }; michael@0: michael@0: // create test data michael@0: var searchTerm = "frecency"; michael@0: var results = []; michael@0: var matchCount = 0; michael@0: var now = Date.now(); michael@0: var prefPrefix = "places.frecency."; michael@0: michael@0: function task_initializeBucket(bucket) { michael@0: let [cutoffName, weightName] = bucket; michael@0: // get pref values michael@0: var weight = 0, cutoff = 0, bonus = 0; michael@0: try { michael@0: weight = prefs.getIntPref(prefPrefix + weightName); michael@0: } catch(ex) {} michael@0: try { michael@0: cutoff = prefs.getIntPref(prefPrefix + cutoffName); michael@0: } catch(ex) {} michael@0: michael@0: if (cutoff < 1) michael@0: return; michael@0: michael@0: // generate a date within the cutoff period michael@0: var dateInPeriod = (now - ((cutoff - 1) * 86400 * 1000)) * 1000; michael@0: michael@0: for (let [bonusName, visitType] in Iterator(bonusPrefs)) { michael@0: var frecency = -1; michael@0: var calculatedURI = null; michael@0: var matchTitle = ""; michael@0: var bonusValue = prefs.getIntPref(prefPrefix + bonusName); michael@0: // unvisited (only for first cutoff date bucket) michael@0: if (bonusName == "unvisitedBookmarkBonus" || bonusName == "unvisitedTypedBonus") { michael@0: if (cutoffName == "firstBucketCutoff") { michael@0: var points = Math.ceil(bonusValue / parseFloat(100.0) * weight); michael@0: var visitCount = 1; //bonusName == "unvisitedBookmarkBonus" ? 1 : 0; michael@0: frecency = Math.ceil(visitCount * points); michael@0: calculatedURI = uri("http://" + searchTerm + ".com/" + michael@0: bonusName + ":" + bonusValue + "/cutoff:" + cutoff + michael@0: "/weight:" + weight + "/frecency:" + frecency); michael@0: if (bonusName == "unvisitedBookmarkBonus") { michael@0: matchTitle = searchTerm + "UnvisitedBookmark"; michael@0: bmsvc.insertBookmark(bmsvc.unfiledBookmarksFolder, calculatedURI, bmsvc.DEFAULT_INDEX, matchTitle); michael@0: } michael@0: else { michael@0: matchTitle = searchTerm + "UnvisitedTyped"; michael@0: yield promiseAddVisits({ michael@0: uri: calculatedURI, michael@0: title: matchTitle, michael@0: transition: visitType, michael@0: visitDate: now michael@0: }); michael@0: histsvc.markPageAsTyped(calculatedURI); michael@0: } michael@0: } michael@0: } michael@0: else { michael@0: // visited michael@0: // visited bookmarks get the visited bookmark bonus twice michael@0: if (visitType == Ci.nsINavHistoryService.TRANSITION_BOOKMARK) michael@0: bonusValue = bonusValue * 2; michael@0: michael@0: var points = Math.ceil(1 * ((bonusValue / parseFloat(100.000000)).toFixed(6) * weight) / 1); michael@0: if (!points) { michael@0: if (visitType == Ci.nsINavHistoryService.TRANSITION_EMBED || michael@0: visitType == Ci.nsINavHistoryService.TRANSITION_FRAMED_LINK || michael@0: visitType == Ci.nsINavHistoryService.TRANSITION_DOWNLOAD || michael@0: bonusName == "defaultVisitBonus") michael@0: frecency = 0; michael@0: else michael@0: frecency = -1; michael@0: } michael@0: else michael@0: frecency = points; michael@0: calculatedURI = uri("http://" + searchTerm + ".com/" + michael@0: bonusName + ":" + bonusValue + "/cutoff:" + cutoff + michael@0: "/weight:" + weight + "/frecency:" + frecency); michael@0: if (visitType == Ci.nsINavHistoryService.TRANSITION_BOOKMARK) { michael@0: matchTitle = searchTerm + "Bookmarked"; michael@0: bmsvc.insertBookmark(bmsvc.unfiledBookmarksFolder, calculatedURI, bmsvc.DEFAULT_INDEX, matchTitle); michael@0: } michael@0: else michael@0: matchTitle = calculatedURI.spec.substr(calculatedURI.spec.lastIndexOf("/")+1); michael@0: yield promiseAddVisits({ michael@0: uri: calculatedURI, michael@0: transition: visitType, michael@0: visitDate: dateInPeriod michael@0: }); michael@0: } michael@0: michael@0: if (calculatedURI && frecency) { michael@0: results.push([calculatedURI, frecency, matchTitle]); michael@0: yield promiseAddVisits({ michael@0: uri: calculatedURI, michael@0: title: matchTitle, michael@0: transition: visitType, michael@0: visitDate: dateInPeriod michael@0: }); michael@0: } michael@0: } michael@0: } michael@0: michael@0: function AutoCompleteInput(aSearches) { michael@0: this.searches = aSearches; michael@0: } michael@0: AutoCompleteInput.prototype = { michael@0: constructor: AutoCompleteInput, michael@0: michael@0: searches: null, michael@0: michael@0: minResultsForPopup: 0, michael@0: timeout: 10, michael@0: searchParam: "", michael@0: textValue: "", michael@0: disableAutoComplete: false, michael@0: completeDefaultIndex: false, michael@0: michael@0: get searchCount() { michael@0: return this.searches.length; michael@0: }, michael@0: michael@0: getSearchAt: function(aIndex) { michael@0: return this.searches[aIndex]; michael@0: }, michael@0: michael@0: onSearchBegin: function() {}, michael@0: onSearchComplete: function() {}, michael@0: michael@0: popupOpen: false, michael@0: michael@0: popup: { michael@0: setSelectedIndex: function(aIndex) {}, michael@0: invalidate: function() {}, michael@0: michael@0: // nsISupports implementation michael@0: QueryInterface: function(iid) { michael@0: if (iid.equals(Ci.nsISupports) || michael@0: iid.equals(Ci.nsIAutoCompletePopup)) michael@0: return this; michael@0: michael@0: throw Components.results.NS_ERROR_NO_INTERFACE; michael@0: } michael@0: }, michael@0: michael@0: // nsISupports implementation michael@0: QueryInterface: function(iid) { michael@0: if (iid.equals(Ci.nsISupports) || michael@0: iid.equals(Ci.nsIAutoCompleteInput)) michael@0: return this; michael@0: michael@0: throw Components.results.NS_ERROR_NO_INTERFACE; michael@0: } michael@0: } michael@0: michael@0: function run_test() michael@0: { michael@0: run_next_test(); michael@0: } michael@0: michael@0: add_task(function test_frecency() michael@0: { michael@0: for (let [, bucket] in Iterator(bucketPrefs)) { michael@0: yield task_initializeBucket(bucket); michael@0: } michael@0: michael@0: // sort results by frecency michael@0: results.sort(function(a,b) b[1] - a[1]); michael@0: // Make sure there's enough results returned michael@0: prefs.setIntPref("browser.urlbar.maxRichResults", results.length); michael@0: michael@0: // DEBUG michael@0: //results.every(function(el) { dump("result: " + el[1] + ": " + el[0].spec + " (" + el[2] + ")\n"); return true; }) michael@0: michael@0: yield promiseAsyncUpdates(); michael@0: michael@0: var controller = Components.classes["@mozilla.org/autocomplete/controller;1"]. michael@0: getService(Components.interfaces.nsIAutoCompleteController); michael@0: michael@0: // Make an AutoCompleteInput that uses our searches michael@0: // and confirms results on search complete michael@0: var input = new AutoCompleteInput(["history"]); michael@0: michael@0: controller.input = input; michael@0: michael@0: // always search in history + bookmarks, no matter what the default is michael@0: prefs.setIntPref("browser.urlbar.search.sources", 3); michael@0: prefs.setIntPref("browser.urlbar.default.behavior", 0); michael@0: michael@0: var numSearchesStarted = 0; michael@0: input.onSearchBegin = function() { michael@0: numSearchesStarted++; michael@0: do_check_eq(numSearchesStarted, 1); michael@0: }; michael@0: michael@0: let deferred = Promise.defer(); michael@0: input.onSearchComplete = function() { michael@0: do_check_eq(numSearchesStarted, 1); michael@0: do_check_eq(controller.searchStatus, michael@0: Ci.nsIAutoCompleteController.STATUS_COMPLETE_MATCH); michael@0: michael@0: // test that all records with non-zero frecency were matched michael@0: do_check_eq(controller.matchCount, results.length); michael@0: michael@0: // test that matches are sorted by frecency michael@0: for (var i = 0; i < controller.matchCount; i++) { michael@0: let searchURL = controller.getValueAt(i); michael@0: let expectURL = results[i][0].spec; michael@0: if (searchURL == expectURL) { michael@0: do_check_eq(controller.getValueAt(i), results[i][0].spec); michael@0: do_check_eq(controller.getCommentAt(i), results[i][2]); michael@0: } else { michael@0: // If the results didn't match exactly, perhaps it's still the right michael@0: // frecency just in the wrong "order" (order of same frecency is michael@0: // undefined), so check if frecency matches. This is okay because we michael@0: // can still ensure the correct number of expected frecencies. michael@0: let getFrecency = function(aURL) aURL.match(/frecency:(-?\d+)$/)[1]; michael@0: print("### checking for same frecency between '" + searchURL + michael@0: "' and '" + expectURL + "'"); michael@0: do_check_eq(getFrecency(searchURL), getFrecency(expectURL)); michael@0: } michael@0: } michael@0: deferred.resolve(); michael@0: }; michael@0: michael@0: controller.startSearch(searchTerm); michael@0: michael@0: yield deferred.promise; michael@0: });