From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- .../urlbar/tests/quicksuggest/unit/head.js | 911 +++++++++++++++++++++ 1 file changed, 911 insertions(+) create mode 100644 browser/components/urlbar/tests/quicksuggest/unit/head.js (limited to 'browser/components/urlbar/tests/quicksuggest/unit/head.js') diff --git a/browser/components/urlbar/tests/quicksuggest/unit/head.js b/browser/components/urlbar/tests/quicksuggest/unit/head.js new file mode 100644 index 0000000000..c468e4526f --- /dev/null +++ b/browser/components/urlbar/tests/quicksuggest/unit/head.js @@ -0,0 +1,911 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* import-globals-from ../../unit/head.js */ +/* eslint-disable jsdoc/require-param */ + +ChromeUtils.defineESModuleGetters(this, { + QuickSuggest: "resource:///modules/QuickSuggest.sys.mjs", + TelemetryTestUtils: "resource://testing-common/TelemetryTestUtils.sys.mjs", + UrlbarProviderAutofill: "resource:///modules/UrlbarProviderAutofill.sys.mjs", + UrlbarProviderQuickSuggest: + "resource:///modules/UrlbarProviderQuickSuggest.sys.mjs", +}); + +add_setup(async function setUpQuickSuggestXpcshellTest() { + // Initializing TelemetryEnvironment in an xpcshell environment requires + // jumping through a bunch of hoops. Suggest's use of TelemetryEnvironment is + // tested in browser tests, and there's no other necessary reason to wait for + // TelemetryEnvironment initialization in xpcshell tests, so just skip it. + UrlbarPrefs._testSkipTelemetryEnvironmentInit = true; +}); + +/** + * Adds two tasks: One with the Rust backend disabled and one with it enabled. + * The names of the task functions will be the name of the passed-in task + * function appended with "_rustDisabled" and "_rustEnabled". If the passed-in + * task doesn't have a name, "anonymousTask" will be used. Call this with the + * usual `add_task()` arguments. + */ +function add_tasks_with_rust(...args) { + let taskFnIndex = args.findIndex(a => typeof a == "function"); + let taskFn = args[taskFnIndex]; + + for (let rustEnabled of [false, true]) { + let newTaskFn = async (...taskFnArgs) => { + info("add_tasks_with_rust: Setting rustEnabled: " + rustEnabled); + UrlbarPrefs.set("quicksuggest.rustEnabled", rustEnabled); + info("add_tasks_with_rust: Done setting rustEnabled: " + rustEnabled); + + // The current backend may now start syncing, so wait for it to finish. + info("add_tasks_with_rust: Forcing sync"); + await QuickSuggestTestUtils.forceSync(); + info("add_tasks_with_rust: Done forcing sync"); + + let rv; + try { + info( + "add_tasks_with_rust: Calling original task function: " + taskFn.name + ); + rv = await taskFn(...taskFnArgs); + } catch (e) { + // Clearly report any unusual errors to make them easier to spot and to + // make the flow of the test clearer. The harness throws NS_ERROR_ABORT + // when a normal assertion fails, so don't report that. + if (e.result != Cr.NS_ERROR_ABORT) { + Assert.ok( + false, + "add_tasks_with_rust: The original task function threw an error: " + + e + ); + } + throw e; + } finally { + info( + "add_tasks_with_rust: Done calling original task function: " + + taskFn.name + ); + info("add_tasks_with_rust: Clearing rustEnabled"); + UrlbarPrefs.clear("quicksuggest.rustEnabled"); + info("add_tasks_with_rust: Done clearing rustEnabled"); + + // The current backend may now start syncing, so wait for it to finish. + info("add_tasks_with_rust: Forcing sync"); + await QuickSuggestTestUtils.forceSync(); + info("add_tasks_with_rust: Done forcing sync"); + } + return rv; + }; + + Object.defineProperty(newTaskFn, "name", { + value: + (taskFn.name || "anonymousTask") + + (rustEnabled ? "_rustEnabled" : "_rustDisabled"), + }); + let addTaskArgs = [...args]; + addTaskArgs[taskFnIndex] = newTaskFn; + add_task(...addTaskArgs); + } +} + +/** + * Returns an expected Wikipedia (non-sponsored) result that can be passed to + * `check_results()` regardless of whether the Rust backend is enabled. + * + * @returns {object} + * An object that can be passed to `check_results()`. + */ +function makeWikipediaResult({ + source, + provider, + keyword = "wikipedia", + title = "Wikipedia Suggestion", + url = "http://example.com/wikipedia", + originalUrl = "http://example.com/wikipedia", + icon = null, + iconBlob = new Blob([new Uint8Array([])]), + impressionUrl = "http://example.com/wikipedia-impression", + clickUrl = "http://example.com/wikipedia-click", + blockId = 1, + advertiser = "Wikipedia", + iabCategory = "5 - Education", + suggestedIndex = -1, + isSuggestedIndexRelativeToGroup = true, +}) { + let result = { + suggestedIndex, + isSuggestedIndexRelativeToGroup, + type: UrlbarUtils.RESULT_TYPE.URL, + source: UrlbarUtils.RESULT_SOURCE.SEARCH, + heuristic: false, + payload: { + title, + url, + originalUrl, + displayUrl: url.replace(/^https:\/\//, ""), + isSponsored: false, + qsSuggestion: keyword, + sponsoredAdvertiser: "Wikipedia", + sponsoredIabCategory: "5 - Education", + helpUrl: QuickSuggest.HELP_URL, + helpL10n: { + id: "urlbar-result-menu-learn-more-about-firefox-suggest", + }, + isBlockable: true, + blockL10n: { + id: "urlbar-result-menu-dismiss-firefox-suggest", + }, + telemetryType: "adm_nonsponsored", + }, + }; + + if (UrlbarPrefs.get("quickSuggestRustEnabled")) { + result.payload.source = source || "rust"; + result.payload.provider = provider || "Wikipedia"; + result.payload.iconBlob = iconBlob; + } else { + result.payload.source = source || "remote-settings"; + result.payload.provider = provider || "AdmWikipedia"; + result.payload.icon = icon; + result.payload.sponsoredImpressionUrl = impressionUrl; + result.payload.sponsoredClickUrl = clickUrl; + result.payload.sponsoredBlockId = blockId; + result.payload.sponsoredAdvertiser = advertiser; + result.payload.sponsoredIabCategory = iabCategory; + } + + return result; +} + +/** + * Returns an expected AMP (sponsored) result that can be passed to + * `check_results()` regardless of whether the Rust backend is enabled. + * + * @returns {object} + * An object that can be passed to `check_results()`. + */ +function makeAmpResult({ + source, + provider, + keyword = "amp", + title = "Amp Suggestion", + url = "http://example.com/amp", + originalUrl = "http://example.com/amp", + icon = null, + iconBlob = new Blob([new Uint8Array([])]), + impressionUrl = "http://example.com/amp-impression", + clickUrl = "http://example.com/amp-click", + blockId = 1, + advertiser = "Amp", + iabCategory = "22 - Shopping", + suggestedIndex = -1, + isSuggestedIndexRelativeToGroup = true, + requestId = undefined, +} = {}) { + let result = { + suggestedIndex, + isSuggestedIndexRelativeToGroup, + type: UrlbarUtils.RESULT_TYPE.URL, + source: UrlbarUtils.RESULT_SOURCE.SEARCH, + heuristic: false, + payload: { + title, + url, + originalUrl, + requestId, + displayUrl: url.replace(/^https:\/\//, ""), + isSponsored: true, + qsSuggestion: keyword, + sponsoredImpressionUrl: impressionUrl, + sponsoredClickUrl: clickUrl, + sponsoredBlockId: blockId, + sponsoredAdvertiser: advertiser, + sponsoredIabCategory: iabCategory, + helpUrl: QuickSuggest.HELP_URL, + helpL10n: { + id: "urlbar-result-menu-learn-more-about-firefox-suggest", + }, + isBlockable: true, + blockL10n: { + id: "urlbar-result-menu-dismiss-firefox-suggest", + }, + telemetryType: "adm_sponsored", + descriptionL10n: { id: "urlbar-result-action-sponsored" }, + }, + }; + + if (UrlbarPrefs.get("quickSuggestRustEnabled")) { + result.payload.source = source || "rust"; + result.payload.provider = provider || "Amp"; + if (result.payload.source == "rust") { + result.payload.iconBlob = iconBlob; + } else { + result.payload.icon = icon; + } + } else { + result.payload.source = source || "remote-settings"; + result.payload.provider = provider || "AdmWikipedia"; + result.payload.icon = icon; + } + + return result; +} + +/** + * Returns an expected MDN result that can be passed to `check_results()` + * regardless of whether the Rust backend is enabled. + * + * @returns {object} + * An object that can be passed to `check_results()`. + */ +function makeMdnResult({ url, title, description }) { + let finalUrl = new URL(url); + finalUrl.searchParams.set("utm_medium", "firefox-desktop"); + finalUrl.searchParams.set("utm_source", "firefox-suggest"); + finalUrl.searchParams.set( + "utm_campaign", + "firefox-mdn-web-docs-suggestion-experiment" + ); + finalUrl.searchParams.set("utm_content", "treatment"); + + let result = { + isBestMatch: true, + suggestedIndex: 1, + type: UrlbarUtils.RESULT_TYPE.URL, + source: UrlbarUtils.RESULT_SOURCE.OTHER_NETWORK, + heuristic: false, + payload: { + telemetryType: "mdn", + title, + url: finalUrl.href, + originalUrl: url, + displayUrl: finalUrl.href.replace(/^https:\/\//, ""), + description, + icon: "chrome://global/skin/icons/mdn.svg", + shouldShowUrl: true, + bottomTextL10n: { id: "firefox-suggest-mdn-bottom-text" }, + }, + }; + + if (UrlbarPrefs.get("quickSuggestRustEnabled")) { + result.payload.source = "rust"; + result.payload.provider = "Mdn"; + } else { + result.payload.source = "remote-settings"; + result.payload.provider = "MDNSuggestions"; + } + + return result; +} + +/** + * Returns an expected AMO (addons) result that can be passed to + * `check_results()` regardless of whether the Rust backend is enabled. + * + * @returns {object} + * An object that can be passed to `check_results()`. + */ +function makeAmoResult({ + source, + provider, + title = "Amo Suggestion", + description = "Amo description", + url = "http://example.com/amo", + originalUrl = "http://example.com/amo", + icon = null, + setUtmParams = true, +}) { + if (setUtmParams) { + url = new URL(url); + url.searchParams.set("utm_medium", "firefox-desktop"); + url.searchParams.set("utm_source", "firefox-suggest"); + url = url.href; + } + + let result = { + isBestMatch: true, + suggestedIndex: 1, + type: UrlbarUtils.RESULT_TYPE.URL, + source: UrlbarUtils.RESULT_SOURCE.SEARCH, + heuristic: false, + payload: { + source, + provider, + title, + description, + url, + originalUrl, + icon, + displayUrl: url.replace(/^https:\/\//, ""), + shouldShowUrl: true, + bottomTextL10n: { id: "firefox-suggest-addons-recommended" }, + helpUrl: QuickSuggest.HELP_URL, + telemetryType: "amo", + }, + }; + + if (UrlbarPrefs.get("quickSuggestRustEnabled")) { + result.payload.source = source || "rust"; + result.payload.provider = provider || "Amo"; + } else { + result.payload.source = source || "remote-settings"; + result.payload.provider = provider || "AddonSuggestions"; + } + + return result; +} + +/** + * Returns an expected weather result that can be passed to `check_results()` + * regardless of whether the Rust backend is enabled. + * + * @returns {object} + * An object that can be passed to `check_results()`. + */ +function makeWeatherResult({ + source, + provider, + telemetryType = undefined, + temperatureUnit = undefined, +} = {}) { + if (!temperatureUnit) { + temperatureUnit = + Services.locale.regionalPrefsLocales[0] == "en-US" ? "f" : "c"; + } + + let result = { + type: UrlbarUtils.RESULT_TYPE.DYNAMIC, + source: UrlbarUtils.RESULT_SOURCE.SEARCH, + heuristic: false, + suggestedIndex: 1, + payload: { + temperatureUnit, + url: MerinoTestUtils.WEATHER_SUGGESTION.url, + iconId: "6", + helpUrl: QuickSuggest.HELP_URL, + requestId: MerinoTestUtils.server.response.body.request_id, + source: "merino", + provider: "accuweather", + dynamicType: "weather", + city: MerinoTestUtils.WEATHER_SUGGESTION.city_name, + temperature: + MerinoTestUtils.WEATHER_SUGGESTION.current_conditions.temperature[ + temperatureUnit + ], + currentConditions: + MerinoTestUtils.WEATHER_SUGGESTION.current_conditions.summary, + forecast: MerinoTestUtils.WEATHER_SUGGESTION.forecast.summary, + high: MerinoTestUtils.WEATHER_SUGGESTION.forecast.high[temperatureUnit], + low: MerinoTestUtils.WEATHER_SUGGESTION.forecast.low[temperatureUnit], + shouldNavigate: true, + }, + }; + + if (UrlbarPrefs.get("quickSuggestRustEnabled")) { + result.payload.source = source || "rust"; + result.payload.provider = provider || "Weather"; + if (telemetryType !== null) { + result.payload.telemetryType = telemetryType || "weather"; + } + } else { + result.payload.source = source || "merino"; + result.payload.provider = provider || "accuweather"; + } + + return result; +} + +/** + * Tests quick suggest prefs migrations. + * + * @param {object} options + * The options object. + * @param {object} options.testOverrides + * An object that modifies how migration is performed. It has the following + * properties, and all are optional: + * + * {number} migrationVersion + * Migration will stop at this version, so for example you can test + * migration only up to version 1 even when the current actual version is + * larger than 1. + * {object} defaultPrefs + * An object that maps pref names (relative to `browser.urlbar`) to + * default-branch values. These should be the default prefs for the given + * `migrationVersion` and will be set as defaults before migration occurs. + * + * @param {string} options.scenario + * The scenario to set at the time migration occurs. + * @param {object} options.expectedPrefs + * The expected prefs after migration: `{ defaultBranch, userBranch }` + * Pref names should be relative to `browser.urlbar`. + * @param {object} [options.initialUserBranch] + * Prefs to set on the user branch before migration ocurs. Use these to + * simulate user actions like disabling prefs or opting in or out of the + * online modal. Pref names should be relative to `browser.urlbar`. + */ +async function doMigrateTest({ + testOverrides, + scenario, + expectedPrefs, + initialUserBranch = {}, +}) { + info( + "Testing migration: " + + JSON.stringify({ + testOverrides, + initialUserBranch, + scenario, + expectedPrefs, + }) + ); + + function setPref(branch, name, value) { + switch (typeof value) { + case "boolean": + branch.setBoolPref(name, value); + break; + case "number": + branch.setIntPref(name, value); + break; + case "string": + branch.setCharPref(name, value); + break; + default: + Assert.ok( + false, + `Pref type not handled for setPref: ${name} = ${value}` + ); + break; + } + } + + function getPref(branch, name) { + let type = typeof UrlbarPrefs.get(name); + switch (type) { + case "boolean": + return branch.getBoolPref(name); + case "number": + return branch.getIntPref(name); + case "string": + return branch.getCharPref(name); + default: + Assert.ok(false, `Pref type not handled for getPref: ${name} ${type}`); + break; + } + return null; + } + + let defaultBranch = Services.prefs.getDefaultBranch("browser.urlbar."); + let userBranch = Services.prefs.getBranch("browser.urlbar."); + + // Set initial prefs. `initialDefaultBranch` are firefox.js values, i.e., + // defaults immediately after startup and before any scenario update and + // migration happens. + UrlbarPrefs._updatingFirefoxSuggestScenario = true; + UrlbarPrefs.clear("quicksuggest.migrationVersion"); + let initialDefaultBranch = { + "suggest.quicksuggest.nonsponsored": false, + "suggest.quicksuggest.sponsored": false, + "quicksuggest.dataCollection.enabled": false, + }; + for (let name of Object.keys(initialDefaultBranch)) { + userBranch.clearUserPref(name); + } + for (let [branch, prefs] of [ + [defaultBranch, initialDefaultBranch], + [userBranch, initialUserBranch], + ]) { + for (let [name, value] of Object.entries(prefs)) { + if (value !== undefined) { + setPref(branch, name, value); + } + } + } + UrlbarPrefs._updatingFirefoxSuggestScenario = false; + + // Update the scenario and check prefs twice. The first time the migration + // should happen, and the second time the migration should not happen and + // all the prefs should stay the same. + for (let i = 0; i < 2; i++) { + info(`Calling updateFirefoxSuggestScenario, i=${i}`); + + // Do the scenario update and set `isStartup` to simulate startup. + await UrlbarPrefs.updateFirefoxSuggestScenario({ + ...testOverrides, + scenario, + isStartup: true, + }); + + // Check expected pref values. Store expected effective values as we go so + // we can check them afterward. For a given pref, the expected effective + // value is the user value, or if there's not a user value, the default + // value. + let expectedEffectivePrefs = {}; + let { + defaultBranch: expectedDefaultBranch, + userBranch: expectedUserBranch, + } = expectedPrefs; + expectedDefaultBranch = expectedDefaultBranch || {}; + expectedUserBranch = expectedUserBranch || {}; + for (let [branch, prefs, branchType] of [ + [defaultBranch, expectedDefaultBranch, "default"], + [userBranch, expectedUserBranch, "user"], + ]) { + let entries = Object.entries(prefs); + if (!entries.length) { + continue; + } + + info( + `Checking expected prefs on ${branchType} branch after updating scenario` + ); + for (let [name, value] of entries) { + expectedEffectivePrefs[name] = value; + if (branch == userBranch) { + Assert.ok( + userBranch.prefHasUserValue(name), + `Pref ${name} is on user branch` + ); + } + Assert.equal( + getPref(branch, name), + value, + `Pref ${name} value on ${branchType} branch` + ); + } + } + + info( + `Making sure prefs on the default branch without expected user-branch values are not on the user branch` + ); + for (let name of Object.keys(initialDefaultBranch)) { + if (!expectedUserBranch.hasOwnProperty(name)) { + Assert.ok( + !userBranch.prefHasUserValue(name), + `Pref ${name} is not on user branch` + ); + } + } + + info(`Checking expected effective prefs`); + for (let [name, value] of Object.entries(expectedEffectivePrefs)) { + Assert.equal( + UrlbarPrefs.get(name), + value, + `Pref ${name} effective value` + ); + } + + let currentVersion = + testOverrides?.migrationVersion === undefined + ? UrlbarPrefs.FIREFOX_SUGGEST_MIGRATION_VERSION + : testOverrides.migrationVersion; + Assert.equal( + UrlbarPrefs.get("quicksuggest.migrationVersion"), + currentVersion, + "quicksuggest.migrationVersion is correct after migration" + ); + } + + // Clean up. + UrlbarPrefs._updatingFirefoxSuggestScenario = true; + UrlbarPrefs.clear("quicksuggest.migrationVersion"); + let userBranchNames = [ + ...Object.keys(initialUserBranch), + ...Object.keys(expectedPrefs.userBranch || {}), + ]; + for (let name of userBranchNames) { + userBranch.clearUserPref(name); + } + UrlbarPrefs._updatingFirefoxSuggestScenario = false; +} + +/** + * Does some "show less frequently" tests where the cap is set in remote + * settings and Nimbus. See `doOneShowLessFrequentlyTest()`. This function + * assumes the matching behavior implemented by the given `BaseFeature` is based + * on matching prefixes of the given keyword starting at the first word. It + * also assumes the `BaseFeature` provides suggestions in remote settings. + * + * @param {object} options + * Options object. + * @param {BaseFeature} options.feature + * The feature that provides the suggestion matched by the searches. + * @param {*} options.expectedResult + * The expected result that should be matched, for searches that are expected + * to match a result. Can also be a function; it's passed the current search + * string and it should return the expected result. + * @param {string} options.showLessFrequentlyCountPref + * The name of the pref that stores the "show less frequently" count being + * tested. + * @param {string} options.nimbusCapVariable + * The name of the Nimbus variable that stores the "show less frequently" cap + * being tested. + * @param {object} options.keyword + * The primary keyword to use during the test. + * @param {number} options.keywordBaseIndex + * The index in `keyword` to base substring checks around. This function will + * test substrings starting at the beginning of keyword and ending at the + * following indexes: one index before `keywordBaseIndex`, + * `keywordBaseIndex`, `keywordBaseIndex` + 1, `keywordBaseIndex` + 2, and + * `keywordBaseIndex` + 3. + */ +async function doShowLessFrequentlyTests({ + feature, + expectedResult, + showLessFrequentlyCountPref, + nimbusCapVariable, + keyword, + keywordBaseIndex = keyword.indexOf(" "), +}) { + // Do some sanity checks on the keyword. Any checks that fail are errors in + // the test. + if (keywordBaseIndex <= 0) { + throw new Error( + "keywordBaseIndex must be > 0, but it's " + keywordBaseIndex + ); + } + if (keyword.length < keywordBaseIndex + 3) { + throw new Error( + "keyword must have at least two chars after keywordBaseIndex" + ); + } + + let tests = [ + { + showLessFrequentlyCount: 0, + canShowLessFrequently: true, + newSearches: { + [keyword.substring(0, keywordBaseIndex - 1)]: false, + [keyword.substring(0, keywordBaseIndex)]: true, + [keyword.substring(0, keywordBaseIndex + 1)]: true, + [keyword.substring(0, keywordBaseIndex + 2)]: true, + [keyword.substring(0, keywordBaseIndex + 3)]: true, + }, + }, + { + showLessFrequentlyCount: 1, + canShowLessFrequently: true, + newSearches: { + [keyword.substring(0, keywordBaseIndex)]: false, + }, + }, + { + showLessFrequentlyCount: 2, + canShowLessFrequently: true, + newSearches: { + [keyword.substring(0, keywordBaseIndex + 1)]: false, + }, + }, + { + showLessFrequentlyCount: 3, + canShowLessFrequently: false, + newSearches: { + [keyword.substring(0, keywordBaseIndex + 2)]: false, + }, + }, + { + showLessFrequentlyCount: 3, + canShowLessFrequently: false, + newSearches: {}, + }, + ]; + + info("Testing 'show less frequently' with cap in remote settings"); + await doOneShowLessFrequentlyTest({ + tests, + feature, + expectedResult, + showLessFrequentlyCountPref, + rs: { + show_less_frequently_cap: 3, + }, + }); + + // Nimbus should override remote settings. + info("Testing 'show less frequently' with cap in Nimbus and remote settings"); + await doOneShowLessFrequentlyTest({ + tests, + feature, + expectedResult, + showLessFrequentlyCountPref, + rs: { + show_less_frequently_cap: 10, + }, + nimbus: { + [nimbusCapVariable]: 3, + }, + }); +} + +/** + * Does a group of searches, increments the "show less frequently" count, and + * repeats until all groups are done. The cap can be set by remote settings + * config and/or Nimbus. + * + * @param {object} options + * Options object. + * @param {BaseFeature} options.feature + * The feature that provides the suggestion matched by the searches. + * @param {*} options.expectedResult + * The expected result that should be matched, for searches that are expected + * to match a result. Can also be a function; it's passed the current search + * string and it should return the expected result. + * @param {string} options.showLessFrequentlyCountPref + * The name of the pref that stores the "show less frequently" count being + * tested. + * @param {object} options.tests + * An array where each item describes a group of new searches to perform and + * expected state. Each item should look like this: + * `{ showLessFrequentlyCount, canShowLessFrequently, newSearches }` + * + * {number} showLessFrequentlyCount + * The expected value of `showLessFrequentlyCount` before the group of + * searches is performed. + * {boolean} canShowLessFrequently + * The expected value of `canShowLessFrequently` before the group of + * searches is performed. + * {object} newSearches + * An object that maps each search string to a boolean that indicates + * whether the first remote settings suggestion should be triggered by the + * search string. Searches are cumulative: The intended use is to pass a + * large initial group of searches in the first search group, and then each + * following `newSearches` is a diff against the previous. + * @param {object} options.rs + * The remote settings config to set. + * @param {object} options.nimbus + * The Nimbus variables to set. + */ +async function doOneShowLessFrequentlyTest({ + feature, + expectedResult, + showLessFrequentlyCountPref, + tests, + rs = {}, + nimbus = {}, +}) { + // Disable Merino so we trigger only remote settings suggestions. The + // `BaseFeature` is expected to add remote settings suggestions using keywords + // start starting with the first word in each full keyword, but the mock + // Merino server will always return whatever suggestion it's told to return + // regardless of the search string. That means Merino will return a suggestion + // for a keyword that's smaller than the first full word. + UrlbarPrefs.set("quicksuggest.dataCollection.enabled", false); + + // Set Nimbus variables and RS config. + let cleanUpNimbus = await UrlbarTestUtils.initNimbusFeature(nimbus); + await QuickSuggestTestUtils.withConfig({ + config: rs, + callback: async () => { + let cumulativeSearches = {}; + + for (let { + showLessFrequentlyCount, + canShowLessFrequently, + newSearches, + } of tests) { + info( + "Starting subtest: " + + JSON.stringify({ + showLessFrequentlyCount, + canShowLessFrequently, + newSearches, + }) + ); + + Assert.equal( + feature.showLessFrequentlyCount, + showLessFrequentlyCount, + "showLessFrequentlyCount should be correct initially" + ); + Assert.equal( + UrlbarPrefs.get(showLessFrequentlyCountPref), + showLessFrequentlyCount, + "Pref should be correct initially" + ); + Assert.equal( + feature.canShowLessFrequently, + canShowLessFrequently, + "canShowLessFrequently should be correct initially" + ); + + // Merge the current `newSearches` object into the cumulative object. + cumulativeSearches = { + ...cumulativeSearches, + ...newSearches, + }; + + for (let [searchString, isExpected] of Object.entries( + cumulativeSearches + )) { + info("Doing search: " + JSON.stringify({ searchString, isExpected })); + + let results = []; + if (isExpected) { + results.push( + typeof expectedResult == "function" + ? expectedResult(searchString) + : expectedResult + ); + } + + await check_results({ + context: createContext(searchString, { + providers: [UrlbarProviderQuickSuggest.name], + isPrivate: false, + }), + matches: results, + }); + } + + feature.incrementShowLessFrequentlyCount(); + } + }, + }); + + await cleanUpNimbus(); + UrlbarPrefs.clear(showLessFrequentlyCountPref); + UrlbarPrefs.set("quicksuggest.dataCollection.enabled", true); +} + +/** + * Queries the Rust component directly and checks the returned suggestions. The + * point is to make sure the Rust backend passes the correct providers to the + * Rust component depending on the types of enabled suggestions. Assuming the + * Rust component isn't buggy, it should return suggestions only for the + * passed-in providers. + * + * @param {object} options + * Options object + * @param {string} options.searchString + * The search string. + * @param {Array} options.tests + * Array of test objects: `{ prefs, expectedUrls }` + * + * For each object, the given prefs are set, the Rust component is queried + * using the given search string, and the URLs of the returned suggestions are + * compared to the given expected URLs (order doesn't matter). + * + * {object} prefs + * An object mapping pref names (relative to `browser.urlbar`) to values. + * These prefs will be set before querying and should be used to enable or + * disable particular types of suggestions. + * {Array} expectedUrls + * An array of the URLs of the suggestions that are expected to be returned. + * The order doesn't matter. + */ +async function doRustProvidersTests({ searchString, tests }) { + UrlbarPrefs.set("quicksuggest.rustEnabled", true); + + for (let { prefs, expectedUrls } of tests) { + info( + "Starting Rust providers test: " + JSON.stringify({ prefs, expectedUrls }) + ); + + info("Setting prefs and forcing sync"); + for (let [name, value] of Object.entries(prefs)) { + UrlbarPrefs.set(name, value); + } + await QuickSuggestTestUtils.forceSync(); + + info("Querying with search string: " + JSON.stringify(searchString)); + let suggestions = await QuickSuggest.rustBackend.query(searchString); + info("Got suggestions: " + JSON.stringify(suggestions)); + + Assert.deepEqual( + suggestions.map(s => s.url).sort(), + expectedUrls.sort(), + "query() should return the expected suggestions (by URL)" + ); + + info("Clearing prefs and forcing sync"); + for (let name of Object.keys(prefs)) { + UrlbarPrefs.clear(name); + } + await QuickSuggestTestUtils.forceSync(); + } + + info("Clearing rustEnabled pref and forcing sync"); + UrlbarPrefs.clear("quicksuggest.rustEnabled"); + await QuickSuggestTestUtils.forceSync(); +} -- cgit v1.2.3