diff options
Diffstat (limited to 'toolkit/components/places/tests/unifiedcomplete/head_autocomplete.js')
-rw-r--r-- | toolkit/components/places/tests/unifiedcomplete/head_autocomplete.js | 505 |
1 files changed, 505 insertions, 0 deletions
diff --git a/toolkit/components/places/tests/unifiedcomplete/head_autocomplete.js b/toolkit/components/places/tests/unifiedcomplete/head_autocomplete.js new file mode 100644 index 000000000..11e917e18 --- /dev/null +++ b/toolkit/components/places/tests/unifiedcomplete/head_autocomplete.js @@ -0,0 +1,505 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var Ci = Components.interfaces; +var Cc = Components.classes; +var Cr = Components.results; +var Cu = Components.utils; + +const FRECENCY_DEFAULT = 10000; + +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://testing-common/httpd.js"); + +// Import common head. +{ + let commonFile = do_get_file("../head_common.js", false); + let uri = Services.io.newFileURI(commonFile); + Services.scriptloader.loadSubScript(uri.spec, this); +} + +// Put any other stuff relative to this test folder below. + +const TITLE_SEARCH_ENGINE_SEPARATOR = " \u00B7\u2013\u00B7 "; + +function run_test() { + run_next_test(); +} + +function* cleanup() { + Services.prefs.clearUserPref("browser.urlbar.autocomplete.enabled"); + Services.prefs.clearUserPref("browser.urlbar.autoFill"); + Services.prefs.clearUserPref("browser.urlbar.autoFill.typed"); + Services.prefs.clearUserPref("browser.urlbar.autoFill.searchEngines"); + let suggestPrefs = [ + "history", + "bookmark", + "history.onlyTyped", + "openpage", + "searches", + ]; + for (let type of suggestPrefs) { + Services.prefs.clearUserPref("browser.urlbar.suggest." + type); + } + Services.prefs.clearUserPref("browser.search.suggest.enabled"); + yield PlacesUtils.bookmarks.eraseEverything(); + yield PlacesTestUtils.clearHistory(); +} +do_register_cleanup(cleanup); + +/** + * @param aSearches + * Array of AutoCompleteSearch names. + */ +function AutoCompleteInput(aSearches) { + this.searches = aSearches; +} +AutoCompleteInput.prototype = { + popup: { + selectedIndex: -1, + invalidate: function () {}, + QueryInterface: XPCOMUtils.generateQI([Ci.nsIAutoCompletePopup]) + }, + popupOpen: false, + + disableAutoComplete: false, + completeDefaultIndex: true, + completeSelectedIndex: true, + forceComplete: false, + + minResultsForPopup: 0, + maxRows: 0, + + showCommentColumn: false, + showImageColumn: false, + + timeout: 10, + searchParam: "", + + get searchCount() { + return this.searches.length; + }, + getSearchAt: function(aIndex) { + return this.searches[aIndex]; + }, + + textValue: "", + // Text selection range + _selStart: 0, + _selEnd: 0, + get selectionStart() { + return this._selStart; + }, + get selectionEnd() { + return this._selEnd; + }, + selectTextRange: function(aStart, aEnd) { + this._selStart = aStart; + this._selEnd = aEnd; + }, + + onSearchBegin: function () {}, + onSearchComplete: function () {}, + + onTextEntered: () => false, + onTextReverted: () => false, + + QueryInterface: XPCOMUtils.generateQI([Ci.nsIAutoCompleteInput]) +} + +// A helper for check_autocomplete to check a specific match against data from +// the controller. +function _check_autocomplete_matches(match, result) { + let { uri, title, tags, style } = match; + if (tags) + title += " \u2013 " + tags.sort().join(", "); + if (style) + style = style.sort(); + else + style = ["favicon"]; + + do_print(`Checking against expected "${uri.spec}", "${title}"`); + // Got a match on both uri and title? + if (stripPrefix(uri.spec) != stripPrefix(result.value) || title != result.comment) { + return false; + } + + let actualStyle = result.style.split(/\s+/).sort(); + if (style) + Assert.equal(actualStyle.toString(), style.toString(), "Match should have expected style"); + if (uri.spec.startsWith("moz-action:")) { + Assert.ok(actualStyle.includes("action"), "moz-action results should always have 'action' in their style"); + } + + if (match.icon) + Assert.equal(result.image, match.icon, "Match should have expected image"); + + return true; +} + +function* check_autocomplete(test) { + // At this point frecency could still be updating due to latest pages + // updates. + // This is not a problem in real life, but autocomplete tests should + // return reliable resultsets, thus we have to wait. + yield PlacesTestUtils.promiseAsyncUpdates(); + + // Make an AutoCompleteInput that uses our searches and confirms results. + let input = new AutoCompleteInput(["unifiedcomplete"]); + input.textValue = test.search; + + if (test.searchParam) + input.searchParam = test.searchParam; + + // Caret must be at the end for autoFill to happen. + let strLen = test.search.length; + input.selectTextRange(strLen, strLen); + Assert.equal(input.selectionStart, strLen, "Selection starts at end"); + Assert.equal(input.selectionEnd, strLen, "Selection ends at the end"); + + let controller = Cc["@mozilla.org/autocomplete/controller;1"] + .getService(Ci.nsIAutoCompleteController); + controller.input = input; + + let numSearchesStarted = 0; + input.onSearchBegin = () => { + do_print("onSearchBegin received"); + numSearchesStarted++; + }; + let searchCompletePromise = new Promise(resolve => { + input.onSearchComplete = () => { + do_print("onSearchComplete received"); + resolve(); + } + }); + let expectedSearches = 1; + if (test.incompleteSearch) { + controller.startSearch(test.incompleteSearch); + expectedSearches++; + } + + do_print("Searching for: '" + test.search + "'"); + controller.startSearch(test.search); + yield searchCompletePromise; + + Assert.equal(numSearchesStarted, expectedSearches, "All searches started"); + + // Check to see the expected uris and titles match up. If 'enable-actions' + // is specified, we check that the first specified match is the first + // controller value (as this is the "special" always selected item), but the + // rest can match in any order. + // If 'enable-actions' is not specified, they can match in any order. + if (test.matches) { + // Do not modify the test original matches. + let matches = test.matches.slice(); + + if (matches.length) { + let firstIndexToCheck = 0; + if (test.searchParam && test.searchParam.includes("enable-actions")) { + firstIndexToCheck = 1; + do_print("Checking first match is first autocomplete entry") + let result = { + value: controller.getValueAt(0), + comment: controller.getCommentAt(0), + style: controller.getStyleAt(0), + image: controller.getImageAt(0), + } + do_print(`First match is "${result.value}", "${result.comment}"`); + Assert.ok(_check_autocomplete_matches(matches[0], result), "first item is correct"); + do_print("Checking rest of the matches"); + } + + for (let i = firstIndexToCheck; i < controller.matchCount; i++) { + let result = { + value: controller.getValueAt(i), + comment: controller.getCommentAt(i), + style: controller.getStyleAt(i), + image: controller.getImageAt(i), + } + do_print(`Looking for "${result.value}", "${result.comment}" in expected results...`); + let lowerBound = test.checkSorting ? i : firstIndexToCheck; + let upperBound = test.checkSorting ? i + 1 : matches.length; + let found = false; + for (let j = lowerBound; j < upperBound; ++j) { + // Skip processed expected results + if (matches[j] == undefined) + continue; + if (_check_autocomplete_matches(matches[j], result)) { + do_print("Got a match at index " + j + "!"); + // Make it undefined so we don't process it again + matches[j] = undefined; + found = true; + break; + } + } + + if (!found) + do_throw(`Didn't find the current result ("${result.value}", "${result.comment}") in matches`); // ' (Emacs syntax highlighting fix) + } + } + + Assert.equal(controller.matchCount, matches.length, + "Got as many results as expected"); + + // If we expect results, make sure we got matches. + do_check_eq(controller.searchStatus, matches.length ? + Ci.nsIAutoCompleteController.STATUS_COMPLETE_MATCH : + Ci.nsIAutoCompleteController.STATUS_COMPLETE_NO_MATCH); + } + + if (test.autofilled) { + // Check the autoFilled result. + Assert.equal(input.textValue, test.autofilled, + "Autofilled value is correct"); + + // Now force completion and check correct casing of the result. + // This ensures the controller is able to do its magic case-preserving + // stuff and correct replacement of the user's casing with result's one. + controller.handleEnter(false); + Assert.equal(input.textValue, test.completed, + "Completed value is correct"); + } +} + +var addBookmark = Task.async(function* (aBookmarkObj) { + Assert.ok(!!aBookmarkObj.uri, "Bookmark object contains an uri"); + let parentId = aBookmarkObj.parentId ? aBookmarkObj.parentId + : PlacesUtils.unfiledBookmarksFolderId; + + let bm = yield PlacesUtils.bookmarks.insert({ + parentGuid: (yield PlacesUtils.promiseItemGuid(parentId)), + title: aBookmarkObj.title || "A bookmark", + url: aBookmarkObj.uri + }); + yield PlacesUtils.promiseItemId(bm.guid); + + if (aBookmarkObj.keyword) { + yield PlacesUtils.keywords.insert({ keyword: aBookmarkObj.keyword, + url: aBookmarkObj.uri.spec, + postData: aBookmarkObj.postData + }); + } + + if (aBookmarkObj.tags) { + PlacesUtils.tagging.tagURI(aBookmarkObj.uri, aBookmarkObj.tags); + } +}); + +function addOpenPages(aUri, aCount=1, aUserContextId=0) { + let ac = Cc["@mozilla.org/autocomplete/search;1?name=unifiedcomplete"] + .getService(Ci.mozIPlacesAutoComplete); + for (let i = 0; i < aCount; i++) { + ac.registerOpenPage(aUri, aUserContextId); + } +} + +function removeOpenPages(aUri, aCount=1, aUserContextId=0) { + let ac = Cc["@mozilla.org/autocomplete/search;1?name=unifiedcomplete"] + .getService(Ci.mozIPlacesAutoComplete); + for (let i = 0; i < aCount; i++) { + ac.unregisterOpenPage(aUri, aUserContextId); + } +} + +function changeRestrict(aType, aChar) { + let branch = "browser.urlbar."; + // "title" and "url" are different from everything else, so special case them. + if (aType == "title" || aType == "url") + branch += "match."; + else + branch += "restrict."; + + do_print("changing restrict for " + aType + " to '" + aChar + "'"); + Services.prefs.setCharPref(branch + aType, aChar); +} + +function resetRestrict(aType) { + let branch = "browser.urlbar."; + // "title" and "url" are different from everything else, so special case them. + if (aType == "title" || aType == "url") + branch += "match."; + else + branch += "restrict."; + + Services.prefs.clearUserPref(branch + aType); +} + +/** + * Strip prefixes from the URI that we don't care about for searching. + * + * @param spec + * The text to modify. + * @return the modified spec. + */ +function stripPrefix(spec) +{ + ["http://", "https://", "ftp://"].some(scheme => { + if (spec.startsWith(scheme)) { + spec = spec.slice(scheme.length); + return true; + } + return false; + }); + + if (spec.startsWith("www.")) { + spec = spec.slice(4); + } + return spec; +} + +function makeActionURI(action, params) { + let encodedParams = {}; + for (let key in params) { + encodedParams[key] = encodeURIComponent(params[key]); + } + let url = "moz-action:" + action + "," + JSON.stringify(encodedParams); + return NetUtil.newURI(url); +} + +// Creates a full "match" entry for a search result, suitable for passing as +// an entry to check_autocomplete. +function makeSearchMatch(input, extra = {}) { + // Note that counter-intuitively, the order the object properties are defined + // in the object passed to makeActionURI is important for check_autocomplete + // to match them :( + let params = { + engineName: extra.engineName || "MozSearch", + input, + searchQuery: "searchQuery" in extra ? extra.searchQuery : input, + }; + if ("alias" in extra) { + // May be undefined, which is expected, but in that case make sure it's not + // included in the params of the moz-action URL. + params.alias = extra.alias; + } + let style = [ "action", "searchengine" ]; + if (Array.isArray(extra.style)) { + style.push(...extra.style); + } + if (extra.heuristic) { + style.push("heuristic"); + } + return { + uri: makeActionURI("searchengine", params), + title: params.engineName, + style, + } +} + +// Creates a full "match" entry for a search result, suitable for passing as +// an entry to check_autocomplete. +function makeVisitMatch(input, url, extra = {}) { + // Note that counter-intuitively, the order the object properties are defined + // in the object passed to makeActionURI is important for check_autocomplete + // to match them :( + let params = { + url, + input, + } + let style = [ "action", "visiturl" ]; + if (extra.heuristic) { + style.push("heuristic"); + } + return { + uri: makeActionURI("visiturl", params), + title: extra.title || url, + style, + } +} + +function makeSwitchToTabMatch(url, extra = {}) { + return { + uri: makeActionURI("switchtab", {url}), + title: extra.title || url, + style: [ "action", "switchtab" ], + } +} + +function makeExtensionMatch(extra = {}) { + let style = [ "action", "extension" ]; + if (extra.heuristic) { + style.push("heuristic"); + } + + return { + uri: makeActionURI("extension", { + content: extra.content, + keyword: extra.keyword, + }), + title: extra.description, + style, + }; +} + +function setFaviconForHref(href, iconHref) { + return new Promise(resolve => { + PlacesUtils.favicons.setAndFetchFaviconForPage( + NetUtil.newURI(href), + NetUtil.newURI(iconHref), + true, + PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, + resolve, + Services.scriptSecurityManager.getSystemPrincipal() + ); + }); +} + +function makeTestServer(port=-1) { + let httpServer = new HttpServer(); + httpServer.start(port); + do_register_cleanup(() => httpServer.stop(() => {})); + return httpServer; +} + +function* addTestEngine(basename, httpServer=undefined) { + httpServer = httpServer || makeTestServer(); + httpServer.registerDirectory("/", do_get_cwd()); + let dataUrl = + "http://localhost:" + httpServer.identity.primaryPort + "/data/"; + + do_print("Adding engine: " + basename); + return yield new Promise(resolve => { + Services.obs.addObserver(function obs(subject, topic, data) { + let engine = subject.QueryInterface(Ci.nsISearchEngine); + do_print("Observed " + data + " for " + engine.name); + if (data != "engine-added" || engine.name != basename) { + return; + } + + Services.obs.removeObserver(obs, "browser-search-engine-modified"); + do_register_cleanup(() => Services.search.removeEngine(engine)); + resolve(engine); + }, "browser-search-engine-modified", false); + + do_print("Adding engine from URL: " + dataUrl + basename); + Services.search.addEngine(dataUrl + basename, null, null, false); + }); +} + +// Ensure we have a default search engine and the keyword.enabled preference +// set. +add_task(function* ensure_search_engine() { + // keyword.enabled is necessary for the tests to see keyword searches. + Services.prefs.setBoolPref("keyword.enabled", true); + + // Initialize the search service, but first set this geo IP pref to a dummy + // string. When the search service is initialized, it contacts the URI named + // in this pref, which breaks the test since outside connections aren't + // allowed. + let geoPref = "browser.search.geoip.url"; + Services.prefs.setCharPref(geoPref, ""); + do_register_cleanup(() => Services.prefs.clearUserPref(geoPref)); + yield new Promise(resolve => { + Services.search.init(resolve); + }); + + // Remove any existing engines before adding ours. + for (let engine of Services.search.getEngines()) { + Services.search.removeEngine(engine); + } + Services.search.addEngineWithDetails("MozSearch", "", "", "", "GET", + "http://s.example.com/search"); + let engine = Services.search.getEngineByName("MozSearch"); + Services.search.currentEngine = engine; +}); |