Source code
Revision control
Copy as Markdown
Other Tools
Test Info: Warnings
- This test gets skipped with pattern: os == 'android'
- Manifest: browser/components/urlbar/tests/quicksuggest/unit/xpcshell.toml
/* 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
// 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: [
{
title: "Pocket Suggestion 0",
description: "Pocket description 0",
lowConfidenceKeywords: [LOW_KEYWORD, "how to low"],
highConfidenceKeywords: [HIGH_KEYWORD],
score: 0.25,
},
{
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],
],
});
});
// Tests the `pocketSuggestIndex` Nimbus variable, which controls the
// group-relative suggestedIndex. The default Pocket suggestedIndex is 0.
add_task(async function nimbusSuggestedIndex() {
const cleanUpNimbusEnable = await UrlbarTestUtils.initNimbusFeature({
pocketSuggestIndex: 0,
});
await QuickSuggestTestUtils.forceSync();
await check_results({
context: createContext(LOW_KEYWORD, {
providers: [UrlbarProviderQuickSuggest.name],
isPrivate: false,
}),
matches: [
makeExpectedResult({
searchString: LOW_KEYWORD,
suggestedIndex: 0,
}),
],
});
await check_results({
context: createContext(HIGH_KEYWORD, {
providers: [UrlbarProviderQuickSuggest.name],
isPrivate: false,
}),
matches: [
makeExpectedResult({
searchString: HIGH_KEYWORD,
suggestedIndex: 1,
isTopPick: true,
}),
],
});
await cleanUpNimbusEnable();
await QuickSuggestTestUtils.forceSync();
await check_results({
context: createContext(LOW_KEYWORD, {
providers: [UrlbarProviderQuickSuggest.name],
isPrivate: false,
}),
matches: [
makeExpectedResult({
searchString: LOW_KEYWORD,
suggestedIndex: -1,
}),
],
});
});
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_tasks_with_rust(
{
skip_if_rust_enabled: true,
},
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() {
// 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,
},
},
{
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,
suggestedIndex,
} = {}) {
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");
let expectedSuggestedIndex = -1;
if (suggestedIndex !== undefined) {
expectedSuggestedIndex = suggestedIndex;
} else if (isTopPick) {
expectedSuggestedIndex = 1;
}
return {
isBestMatch: isTopPick,
suggestedIndex: expectedSuggestedIndex,
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,
},
},
},
};
}