diff options
Diffstat (limited to 'browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_pocket.js')
-rw-r--r-- | browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_pocket.js | 531 |
1 files changed, 531 insertions, 0 deletions
diff --git a/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_pocket.js b/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_pocket.js new file mode 100644 index 0000000000..29133a8579 --- /dev/null +++ b/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_pocket.js @@ -0,0 +1,531 @@ +/* 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/. */ + +// Tests Pocket quick suggest results. + +"use strict"; + +const LOW_KEYWORD = "low one two"; +const HIGH_KEYWORD = "high three"; + +const REMOTE_SETTINGS_DATA = [ + { + type: "pocket-suggestions", + attachment: [ + { + url: "https://example.com/pocket-0", + title: "Pocket Suggestion 0", + description: "Pocket description 0", + lowConfidenceKeywords: [LOW_KEYWORD, "how to low"], + highConfidenceKeywords: [HIGH_KEYWORD], + score: 0.25, + }, + { + url: "https://example.com/pocket-1", + title: "Pocket Suggestion 1", + description: "Pocket description 1", + lowConfidenceKeywords: ["other low"], + highConfidenceKeywords: ["another high"], + score: 0.25, + }, + ], + }, +]; + +add_setup(async () => { + // Disable search suggestions so we don't hit the network. + Services.prefs.setBoolPref("browser.search.suggest.enabled", false); + + await QuickSuggestTestUtils.ensureQuickSuggestInit({ + remoteSettingsRecords: REMOTE_SETTINGS_DATA, + prefs: [ + ["suggest.quicksuggest.nonsponsored", true], + ["pocket.featureGate", true], + ], + }); +}); + +add_task(async function telemetryType() { + Assert.equal( + QuickSuggest.getFeature("PocketSuggestions").getSuggestionTelemetryType({}), + "pocket", + "Telemetry type should be 'pocket'" + ); +}); + +// When non-sponsored suggestions are disabled, Pocket suggestions should be +// disabled. +add_tasks_with_rust(async function nonsponsoredDisabled() { + // Disable sponsored suggestions. Pocket suggestions are non-sponsored, so + // doing this should not prevent them from being enabled. + UrlbarPrefs.set("suggest.quicksuggest.sponsored", false); + + // First make sure the suggestion is added when non-sponsored suggestions are + // enabled. + await check_results({ + context: createContext(LOW_KEYWORD, { + providers: [UrlbarProviderQuickSuggest.name], + isPrivate: false, + }), + matches: [makeExpectedResult({ searchString: LOW_KEYWORD })], + }); + + // Now disable them. + UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", false); + await check_results({ + context: createContext(LOW_KEYWORD, { + providers: [UrlbarProviderQuickSuggest.name], + isPrivate: false, + }), + matches: [], + }); + + UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", true); + UrlbarPrefs.clear("suggest.quicksuggest.sponsored"); + await QuickSuggestTestUtils.forceSync(); +}); + +// When Pocket-specific preferences are disabled, suggestions should not be +// added. +add_tasks_with_rust(async function pocketSpecificPrefsDisabled() { + const prefs = ["suggest.pocket", "pocket.featureGate"]; + for (const pref of prefs) { + // First make sure the suggestion is added. + await check_results({ + context: createContext(LOW_KEYWORD, { + providers: [UrlbarProviderQuickSuggest.name], + isPrivate: false, + }), + matches: [makeExpectedResult({ searchString: LOW_KEYWORD })], + }); + + // Now disable the pref. + UrlbarPrefs.set(pref, false); + await check_results({ + context: createContext(LOW_KEYWORD, { + providers: [UrlbarProviderQuickSuggest.name], + isPrivate: false, + }), + matches: [], + }); + + // Revert. + UrlbarPrefs.set(pref, true); + await QuickSuggestTestUtils.forceSync(); + } +}); + +// Check wheather the Pocket suggestions will be shown by the setup of Nimbus +// variable. +add_tasks_with_rust(async function nimbus() { + // Disable the fature gate. + UrlbarPrefs.set("pocket.featureGate", false); + await check_results({ + context: createContext(LOW_KEYWORD, { + providers: [UrlbarProviderQuickSuggest.name], + isPrivate: false, + }), + matches: [], + }); + + // Enable by Nimbus. + const cleanUpNimbusEnable = await UrlbarTestUtils.initNimbusFeature({ + pocketFeatureGate: true, + }); + await QuickSuggestTestUtils.forceSync(); + await check_results({ + context: createContext(LOW_KEYWORD, { + providers: [UrlbarProviderQuickSuggest.name], + isPrivate: false, + }), + matches: [makeExpectedResult({ searchString: LOW_KEYWORD })], + }); + await cleanUpNimbusEnable(); + + // Enable locally. + UrlbarPrefs.set("pocket.featureGate", true); + await QuickSuggestTestUtils.forceSync(); + + // Disable by Nimbus. + const cleanUpNimbusDisable = await UrlbarTestUtils.initNimbusFeature({ + pocketFeatureGate: false, + }); + await check_results({ + context: createContext(LOW_KEYWORD, { + providers: [UrlbarProviderQuickSuggest.name], + isPrivate: false, + }), + matches: [], + }); + await cleanUpNimbusDisable(); + + // Revert. + UrlbarPrefs.set("pocket.featureGate", true); + await QuickSuggestTestUtils.forceSync(); +}); + +// The suggestion should be shown as a top pick when a high-confidence keyword +// is matched. +add_tasks_with_rust(async function topPick() { + await check_results({ + context: createContext(HIGH_KEYWORD, { + providers: [UrlbarProviderQuickSuggest.name], + isPrivate: false, + }), + matches: [ + makeExpectedResult({ searchString: HIGH_KEYWORD, isTopPick: true }), + ], + }); +}); + +// Low-confidence keywords should do prefix matching starting at the first word. +add_tasks_with_rust(async function lowPrefixes() { + // search string -> should match + let tests = { + l: false, + lo: false, + low: true, + "low ": true, + "low o": true, + "low on": true, + "low one": true, + "low one ": true, + "low one t": true, + "low one tw": true, + "low one two": true, + "low one two ": false, + }; + for (let [searchString, shouldMatch] of Object.entries(tests)) { + info("Doing search: " + JSON.stringify({ searchString, shouldMatch })); + await check_results({ + context: createContext(searchString, { + providers: [UrlbarProviderQuickSuggest.name], + isPrivate: false, + }), + matches: shouldMatch + ? [makeExpectedResult({ searchString, fullKeyword: LOW_KEYWORD })] + : [], + }); + } +}); + +// Low-confidence keywords that start with "how to" should do prefix matching +// starting at "how to" instead of the first word. +// +// Note: The Rust implementation doesn't support this. +add_task( + { + skip_if: () => UrlbarPrefs.get("quickSuggestRustEnabled"), + }, + async function lowPrefixes_howTo() { + // search string -> should match + let tests = { + h: false, + ho: false, + how: false, + "how ": false, + "how t": false, + "how to": true, + "how to ": true, + "how to l": true, + "how to lo": true, + "how to low": true, + }; + for (let [searchString, shouldMatch] of Object.entries(tests)) { + info("Doing search: " + JSON.stringify({ searchString, shouldMatch })); + await check_results({ + context: createContext(searchString, { + providers: [UrlbarProviderQuickSuggest.name], + isPrivate: false, + }), + matches: shouldMatch + ? [makeExpectedResult({ searchString, fullKeyword: "how to low" })] + : [], + }); + } + } +); + +// High-confidence keywords should not do prefix matching at all. +add_tasks_with_rust(async function highPrefixes() { + // search string -> should match + let tests = { + h: false, + hi: false, + hig: false, + high: false, + "high ": false, + "high t": false, + "high th": false, + "high thr": false, + "high thre": false, + "high three": true, + "high three ": false, + }; + for (let [searchString, shouldMatch] of Object.entries(tests)) { + info("Doing search: " + JSON.stringify({ searchString, shouldMatch })); + await check_results({ + context: createContext(searchString, { + providers: [UrlbarProviderQuickSuggest.name], + isPrivate: false, + }), + matches: shouldMatch + ? [ + makeExpectedResult({ + searchString, + fullKeyword: HIGH_KEYWORD, + isTopPick: true, + }), + ] + : [], + }); + } +}); + +// Keyword matching should be case insenstive. +add_tasks_with_rust(async function uppercase() { + await check_results({ + context: createContext(LOW_KEYWORD.toUpperCase(), { + providers: [UrlbarProviderQuickSuggest.name], + isPrivate: false, + }), + matches: [ + makeExpectedResult({ + searchString: LOW_KEYWORD.toUpperCase(), + fullKeyword: LOW_KEYWORD, + }), + ], + }); + await check_results({ + context: createContext(HIGH_KEYWORD.toUpperCase(), { + providers: [UrlbarProviderQuickSuggest.name], + isPrivate: false, + }), + matches: [ + makeExpectedResult({ + searchString: HIGH_KEYWORD.toUpperCase(), + fullKeyword: HIGH_KEYWORD, + isTopPick: true, + }), + ], + }); +}); + +// Tests the "Not relevant" command: a dismissed suggestion shouldn't be added. +add_tasks_with_rust(async function notRelevant() { + let result = makeExpectedResult({ searchString: LOW_KEYWORD }); + + info("Triggering the 'Not relevant' command"); + QuickSuggest.getFeature("PocketSuggestions").handleCommand( + { + controller: { removeResult() {} }, + }, + result, + "not_relevant" + ); + await QuickSuggest.blockedSuggestions._test_readyPromise; + + Assert.ok( + await QuickSuggest.blockedSuggestions.has(result.payload.originalUrl), + "The result's URL should be blocked" + ); + + info("Doing search for blocked suggestion"); + await check_results({ + context: createContext(LOW_KEYWORD, { + providers: [UrlbarProviderQuickSuggest.name], + isPrivate: false, + }), + matches: [], + }); + + info("Doing search for blocked suggestion using high-confidence keyword"); + await check_results({ + context: createContext(HIGH_KEYWORD, { + providers: [UrlbarProviderQuickSuggest.name], + isPrivate: false, + }), + matches: [], + }); + + info("Doing search for a suggestion that wasn't blocked"); + await check_results({ + context: createContext("other low", { + providers: [UrlbarProviderQuickSuggest.name], + isPrivate: false, + }), + matches: [ + makeExpectedResult({ + searchString: "other low", + suggestion: REMOTE_SETTINGS_DATA[0].attachment[1], + }), + ], + }); + + info("Clearing blocked suggestions"); + await QuickSuggest.blockedSuggestions.clear(); + + info("Doing search for unblocked suggestion"); + await check_results({ + context: createContext(LOW_KEYWORD, { + providers: [UrlbarProviderQuickSuggest.name], + isPrivate: false, + }), + matches: [result], + }); +}); + +// Tests the "Not interested" command: all Pocket suggestions should be disabled +// and not added anymore. +add_tasks_with_rust(async function notInterested() { + let result = makeExpectedResult({ searchString: LOW_KEYWORD }); + + info("Triggering the 'Not interested' command"); + QuickSuggest.getFeature("PocketSuggestions").handleCommand( + { + controller: { removeResult() {} }, + }, + result, + "not_interested" + ); + + Assert.ok( + !UrlbarPrefs.get("suggest.pocket"), + "Pocket suggestions should be disabled" + ); + + info("Doing search for the suggestion the command was used on"); + await check_results({ + context: createContext(LOW_KEYWORD, { + providers: [UrlbarProviderQuickSuggest.name], + isPrivate: false, + }), + matches: [], + }); + + info("Doing search for another Pocket suggestion"); + await check_results({ + context: createContext("other low", { + providers: [UrlbarProviderQuickSuggest.name], + isPrivate: false, + }), + matches: [], + }); + + UrlbarPrefs.clear("suggest.pocket"); + await QuickSuggestTestUtils.forceSync(); +}); + +// Tests the "show less frequently" behavior. +add_tasks_with_rust(async function showLessFrequently() { + await doShowLessFrequentlyTests({ + feature: QuickSuggest.getFeature("PocketSuggestions"), + showLessFrequentlyCountPref: "pocket.showLessFrequentlyCount", + nimbusCapVariable: "pocketShowLessFrequentlyCap", + expectedResult: searchString => + makeExpectedResult({ searchString, fullKeyword: LOW_KEYWORD }), + keyword: LOW_KEYWORD, + }); +}); + +// The `Pocket` Rust provider should be passed to the Rust component when +// querying depending on whether Pocket suggestions are enabled. +add_task(async function rustProviders() { + // TODO bug 1874074: The Rust component fetches Pocket suggestions when the + // AMO provider is specified regardless of whether the Pocket provider is + // specified. AMO suggestions are enabled by default, so disable them first so + // that the Rust backend does not pass in the AMO provider. + UrlbarPrefs.set("suggest.addons", false); + + await doRustProvidersTests({ + searchString: LOW_KEYWORD, + tests: [ + { + prefs: { + "suggest.pocket": true, + }, + expectedUrls: ["https://example.com/pocket-0"], + }, + { + prefs: { + "suggest.pocket": false, + }, + expectedUrls: [], + }, + ], + }); + + UrlbarPrefs.clear("suggest.addons"); + UrlbarPrefs.clear("suggest.pocket"); + await QuickSuggestTestUtils.forceSync(); +}); + +function makeExpectedResult({ + searchString, + fullKeyword = searchString, + suggestion = REMOTE_SETTINGS_DATA[0].attachment[0], + source = "remote-settings", + isTopPick = false, +} = {}) { + if ( + source == "remote-settings" && + UrlbarPrefs.get("quicksuggest.rustEnabled") + ) { + source = "rust"; + } + + let provider; + let keywordSubstringNotTyped = fullKeyword.substring(searchString.length); + let description = suggestion.description; + switch (source) { + case "remote-settings": + provider = "PocketSuggestions"; + break; + case "rust": + provider = "Pocket"; + // Rust suggestions currently do not include full keyword or description. + keywordSubstringNotTyped = ""; + description = suggestion.title; + break; + case "merino": + provider = "pocket"; + break; + } + + let url = new URL(suggestion.url); + url.searchParams.set("utm_medium", "firefox-desktop"); + url.searchParams.set("utm_source", "firefox-suggest"); + url.searchParams.set("utm_campaign", "pocket-collections-in-the-address-bar"); + url.searchParams.set("utm_content", "treatment"); + + return { + isBestMatch: isTopPick, + suggestedIndex: isTopPick ? 1 : -1, + type: UrlbarUtils.RESULT_TYPE.URL, + source: UrlbarUtils.RESULT_SOURCE.OTHER_NETWORK, + heuristic: false, + payload: { + source, + provider, + telemetryType: "pocket", + title: suggestion.title, + url: url.href, + displayUrl: url.href.replace(/^https:\/\//, ""), + originalUrl: suggestion.url, + description: isTopPick ? description : "", + icon: isTopPick + ? "chrome://global/skin/icons/pocket.svg" + : "chrome://global/skin/icons/pocket-favicon.ico", + helpUrl: QuickSuggest.HELP_URL, + shouldShowUrl: true, + bottomTextL10n: { + id: "firefox-suggest-pocket-bottom-text", + args: { + keywordSubstringTyped: searchString, + keywordSubstringNotTyped, + }, + }, + }, + }; +} |