summaryrefslogtreecommitdiffstats
path: root/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest_indexes.js
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest_indexes.js')
-rw-r--r--browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest_indexes.js425
1 files changed, 425 insertions, 0 deletions
diff --git a/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest_indexes.js b/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest_indexes.js
new file mode 100644
index 0000000000..282e1a2ba0
--- /dev/null
+++ b/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest_indexes.js
@@ -0,0 +1,425 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests the configurable indexes of sponsored and non-sponsored ("Firefox
+// Suggest") quick suggest results.
+
+"use strict";
+
+const SUGGESTIONS_FIRST_PREF = "browser.urlbar.showSearchSuggestionsFirst";
+const SUGGESTIONS_PREF = "browser.urlbar.suggest.searches";
+
+const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml";
+const MAX_RESULTS = UrlbarPrefs.get("maxRichResults");
+
+const SPONSORED_INDEX_PREF = "browser.urlbar.quicksuggest.sponsoredIndex";
+const NON_SPONSORED_INDEX_PREF =
+ "browser.urlbar.quicksuggest.nonSponsoredIndex";
+
+const SPONSORED_SEARCH_STRING = "frabbits";
+const NON_SPONSORED_SEARCH_STRING = "nonspon";
+
+const TEST_URL = "http://example.com/quicksuggest";
+
+const REMOTE_SETTINGS_RESULTS = [
+ {
+ id: 1,
+ url: `${TEST_URL}?q=${SPONSORED_SEARCH_STRING}`,
+ title: "frabbits",
+ keywords: [SPONSORED_SEARCH_STRING],
+ click_url: "http://click.reporting.test.com/",
+ impression_url: "http://impression.reporting.test.com/",
+ advertiser: "TestAdvertiser",
+ },
+ {
+ id: 2,
+ url: `${TEST_URL}?q=${NON_SPONSORED_SEARCH_STRING}`,
+ title: "Non-Sponsored",
+ keywords: [NON_SPONSORED_SEARCH_STRING],
+ click_url: "http://click.reporting.test.com/nonsponsored",
+ impression_url: "http://impression.reporting.test.com/nonsponsored",
+ advertiser: "TestAdvertiserNonSponsored",
+ iab_category: "5 - Education",
+ },
+];
+
+add_setup(async function () {
+ // This test intermittently times out on Mac TV WebRender.
+ if (AppConstants.platform == "macosx") {
+ requestLongerTimeout(3);
+ }
+
+ await PlacesUtils.history.clear();
+ await PlacesUtils.bookmarks.eraseEverything();
+ await UrlbarTestUtils.formHistory.clear();
+
+ // Add a mock engine so we don't hit the network.
+ await SearchTestUtils.installSearchExtension({}, { setAsDefault: true });
+
+ await QuickSuggestTestUtils.ensureQuickSuggestInit({
+ remoteSettingsResults: [
+ {
+ type: "data",
+ attachment: REMOTE_SETTINGS_RESULTS,
+ },
+ ],
+ });
+
+ registerCleanupFunction(async () => {
+ await PlacesUtils.history.clear();
+ });
+});
+
+// Tests with history only
+add_task(async function noSuggestions() {
+ await doTestPermutations(({ withHistory, generalIndex }) => ({
+ expectedResultCount: withHistory ? MAX_RESULTS : 2,
+ expectedIndex: generalIndex == 0 || !withHistory ? 1 : MAX_RESULTS - 1,
+ }));
+});
+
+// Tests with suggestions followed by history
+add_task(async function suggestionsFirst() {
+ await SpecialPowers.pushPrefEnv({
+ set: [[SUGGESTIONS_FIRST_PREF, true]],
+ });
+ await withSuggestions(async () => {
+ await doTestPermutations(({ withHistory, generalIndex }) => ({
+ expectedResultCount: withHistory ? MAX_RESULTS : 4,
+ expectedIndex: generalIndex == 0 || !withHistory ? 3 : MAX_RESULTS - 1,
+ }));
+ });
+ await SpecialPowers.popPrefEnv();
+});
+
+// Tests with history followed by suggestions
+add_task(async function suggestionsLast() {
+ await SpecialPowers.pushPrefEnv({
+ set: [[SUGGESTIONS_FIRST_PREF, false]],
+ });
+ await withSuggestions(async () => {
+ await doTestPermutations(({ withHistory, generalIndex }) => ({
+ expectedResultCount: withHistory ? MAX_RESULTS : 4,
+ expectedIndex: generalIndex == 0 || !withHistory ? 1 : MAX_RESULTS - 3,
+ }));
+ });
+ await SpecialPowers.popPrefEnv();
+});
+
+// Tests with history only plus a suggestedIndex result with a resultSpan
+add_task(async function otherSuggestedIndex_noSuggestions() {
+ await doSuggestedIndexTest([
+ // heuristic
+ { heuristic: true },
+ // TestProvider result
+ { suggestedIndex: 1, resultSpan: 2 },
+ // history
+ { type: UrlbarUtils.RESULT_TYPE.URL },
+ { type: UrlbarUtils.RESULT_TYPE.URL },
+ { type: UrlbarUtils.RESULT_TYPE.URL },
+ { type: UrlbarUtils.RESULT_TYPE.URL },
+ { type: UrlbarUtils.RESULT_TYPE.URL },
+ { type: UrlbarUtils.RESULT_TYPE.URL },
+ // quick suggest
+ {
+ type: UrlbarUtils.RESULT_TYPE.URL,
+ providerName: UrlbarProviderQuickSuggest.name,
+ },
+ ]);
+});
+
+// Tests with suggestions followed by history plus a suggestedIndex result with
+// a resultSpan
+add_task(async function otherSuggestedIndex_suggestionsFirst() {
+ await SpecialPowers.pushPrefEnv({
+ set: [[SUGGESTIONS_FIRST_PREF, true]],
+ });
+ await withSuggestions(async () => {
+ await doSuggestedIndexTest([
+ // heuristic
+ { heuristic: true },
+ // TestProvider result
+ { suggestedIndex: 1, resultSpan: 2 },
+ // search suggestions
+ {
+ type: UrlbarUtils.RESULT_TYPE.SEARCH,
+ payload: { suggestion: SPONSORED_SEARCH_STRING + "foo" },
+ },
+ {
+ type: UrlbarUtils.RESULT_TYPE.SEARCH,
+ payload: { suggestion: SPONSORED_SEARCH_STRING + "bar" },
+ },
+ // history
+ { type: UrlbarUtils.RESULT_TYPE.URL },
+ { type: UrlbarUtils.RESULT_TYPE.URL },
+ { type: UrlbarUtils.RESULT_TYPE.URL },
+ { type: UrlbarUtils.RESULT_TYPE.URL },
+ // quick suggest
+ {
+ type: UrlbarUtils.RESULT_TYPE.URL,
+ providerName: UrlbarProviderQuickSuggest.name,
+ },
+ ]);
+ });
+ await SpecialPowers.popPrefEnv();
+});
+
+// Tests with history followed by suggestions plus a suggestedIndex result with
+// a resultSpan
+add_task(async function otherSuggestedIndex_suggestionsLast() {
+ await SpecialPowers.pushPrefEnv({
+ set: [[SUGGESTIONS_FIRST_PREF, false]],
+ });
+ await withSuggestions(async () => {
+ await doSuggestedIndexTest([
+ // heuristic
+ { heuristic: true },
+ // TestProvider result
+ { suggestedIndex: 1, resultSpan: 2 },
+ // history
+ { type: UrlbarUtils.RESULT_TYPE.URL },
+ { type: UrlbarUtils.RESULT_TYPE.URL },
+ { type: UrlbarUtils.RESULT_TYPE.URL },
+ { type: UrlbarUtils.RESULT_TYPE.URL },
+ // quick suggest
+ {
+ type: UrlbarUtils.RESULT_TYPE.URL,
+ providerName: UrlbarProviderQuickSuggest.name,
+ },
+ // search suggestions
+ {
+ type: UrlbarUtils.RESULT_TYPE.SEARCH,
+ payload: { suggestion: SPONSORED_SEARCH_STRING + "foo" },
+ },
+ {
+ type: UrlbarUtils.RESULT_TYPE.SEARCH,
+ payload: { suggestion: SPONSORED_SEARCH_STRING + "bar" },
+ },
+ ]);
+ });
+ await SpecialPowers.popPrefEnv();
+});
+
+/**
+ * A test provider that returns one result with a suggestedIndex and resultSpan.
+ */
+class TestProvider extends UrlbarTestUtils.TestProvider {
+ constructor() {
+ super({
+ results: [
+ Object.assign(
+ new UrlbarResult(
+ UrlbarUtils.RESULT_TYPE.URL,
+ UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
+ { url: "http://example.com/test" }
+ ),
+ {
+ suggestedIndex: 1,
+ resultSpan: 2,
+ }
+ ),
+ ],
+ });
+ }
+}
+
+/**
+ * Does a round of test permutations.
+ *
+ * @param {Function} callback
+ * For each permutation, this will be called with the arguments of `doTest()`,
+ * and it should return an object with the appropriate values of
+ * `expectedResultCount` and `expectedIndex`.
+ */
+async function doTestPermutations(callback) {
+ for (let isSponsored of [true, false]) {
+ for (let withHistory of [true, false]) {
+ for (let generalIndex of [0, -1]) {
+ let opts = {
+ isSponsored,
+ withHistory,
+ generalIndex,
+ };
+ await doTest(Object.assign(opts, callback(opts)));
+ }
+ }
+ }
+}
+
+/**
+ * Does one test run.
+ *
+ * @param {object} options
+ * Options for the test.
+ * @param {boolean} options.isSponsored
+ * True to use a sponsored result, false to use a non-sponsored result.
+ * @param {boolean} options.withHistory
+ * True to run with a bunch of history, false to run with no history.
+ * @param {number} options.generalIndex
+ * The value to set as the relevant index pref, i.e., the index within the
+ * general group of the quick suggest result.
+ * @param {number} options.expectedResultCount
+ * The expected total result count for sanity checking.
+ * @param {number} options.expectedIndex
+ * The expected index of the quick suggest result in the whole results list.
+ */
+async function doTest({
+ isSponsored,
+ withHistory,
+ generalIndex,
+ expectedResultCount,
+ expectedIndex,
+}) {
+ info(
+ "Running test with options: " +
+ JSON.stringify({
+ isSponsored,
+ withHistory,
+ generalIndex,
+ expectedResultCount,
+ expectedIndex,
+ })
+ );
+
+ // Set the index pref.
+ let indexPref = isSponsored ? SPONSORED_INDEX_PREF : NON_SPONSORED_INDEX_PREF;
+ await SpecialPowers.pushPrefEnv({
+ set: [[indexPref, generalIndex]],
+ });
+
+ // Add history.
+ if (withHistory) {
+ await addHistory();
+ }
+
+ // Do a search.
+ let value = isSponsored
+ ? SPONSORED_SEARCH_STRING
+ : NON_SPONSORED_SEARCH_STRING;
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value,
+ });
+
+ // Check the result count and quick suggest result.
+ Assert.equal(
+ UrlbarTestUtils.getResultCount(window),
+ expectedResultCount,
+ "Expected result count"
+ );
+ await QuickSuggestTestUtils.assertIsQuickSuggest({
+ window,
+ isSponsored,
+ index: expectedIndex,
+ url: isSponsored
+ ? `${TEST_URL}?q=${SPONSORED_SEARCH_STRING}`
+ : `${TEST_URL}?q=${NON_SPONSORED_SEARCH_STRING}`,
+ });
+
+ await UrlbarTestUtils.promisePopupClose(window);
+ await PlacesUtils.history.clear();
+ await SpecialPowers.popPrefEnv();
+}
+
+/**
+ * Adds history that matches the sponsored and non-sponsored search strings.
+ */
+async function addHistory() {
+ for (let i = 0; i < MAX_RESULTS; i++) {
+ await PlacesTestUtils.addVisits([
+ "http://example.com/" + SPONSORED_SEARCH_STRING + i,
+ "http://example.com/" + NON_SPONSORED_SEARCH_STRING + i,
+ ]);
+ }
+}
+
+/**
+ * Adds a search engine that provides suggestions, calls your callback, and then
+ * removes the engine.
+ *
+ * @param {Function} callback
+ * Your callback function.
+ */
+async function withSuggestions(callback) {
+ await SpecialPowers.pushPrefEnv({
+ set: [[SUGGESTIONS_PREF, true]],
+ });
+ let engine = await SearchTestUtils.promiseNewSearchEngine({
+ url: getRootDirectory(gTestPath) + TEST_ENGINE_BASENAME,
+ });
+ let oldDefaultEngine = await Services.search.getDefault();
+ await Services.search.setDefault(
+ engine,
+ Ci.nsISearchService.CHANGE_REASON_UNKNOWN
+ );
+ try {
+ await callback(engine);
+ } finally {
+ await Services.search.setDefault(
+ oldDefaultEngine,
+ Ci.nsISearchService.CHANGE_REASON_UNKNOWN
+ );
+ await Services.search.removeEngine(engine);
+ await SpecialPowers.popPrefEnv();
+ }
+}
+
+/**
+ * Registers a test provider that returns a result with a suggestedIndex and
+ * resultSpan and asserts the given expected results match the actual results.
+ *
+ * @param {Array} expectedProps
+ * See `checkResults()`.
+ */
+async function doSuggestedIndexTest(expectedProps) {
+ await addHistory();
+ let provider = new TestProvider();
+ UrlbarProvidersManager.registerProvider(provider);
+
+ let context = await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: SPONSORED_SEARCH_STRING,
+ });
+ checkResults(context.results, expectedProps);
+ await UrlbarTestUtils.promisePopupClose(window);
+
+ UrlbarProvidersManager.unregisterProvider(provider);
+ await PlacesUtils.history.clear();
+}
+
+/**
+ * Asserts the given actual and expected results match.
+ *
+ * @param {Array} actualResults
+ * Array of actual results.
+ * @param {Array} expectedProps
+ * Array of expected result-like objects. Only the properties defined in each
+ * of these objects are compared against the corresponding actual result.
+ */
+function checkResults(actualResults, expectedProps) {
+ Assert.equal(
+ actualResults.length,
+ expectedProps.length,
+ "Expected result count"
+ );
+
+ let actualProps = actualResults.map((actual, i) => {
+ if (expectedProps.length <= i) {
+ return actual;
+ }
+ let props = {};
+ let expected = expectedProps[i];
+ for (let [key, expectedValue] of Object.entries(expected)) {
+ if (key != "payload") {
+ props[key] = actual[key];
+ } else {
+ props.payload = {};
+ for (let pkey of Object.keys(expectedValue)) {
+ props.payload[pkey] = actual.payload[pkey];
+ }
+ }
+ }
+ return props;
+ });
+ Assert.deepEqual(actualProps, expectedProps);
+}